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    signature::{AuthenticatorTrait, GenericSignature, VerifyParams},
33    signature_verification::VerifiedDigestCache,
34    zk_login_authenticator::ZkLoginAuthenticator,
35};
36
37#[cfg(test)]
38#[path = "unit_tests/multisig_tests.rs"]
39mod multisig_tests;
40
41pub type WeightUnit = u8;
42pub type ThresholdUnit = u16;
43pub type BitmapUnit = u16;
44pub const MAX_SIGNER_IN_MULTISIG: usize = 10;
45pub const MAX_BITMAP_VALUE: BitmapUnit = 0b1111111111;
46/// The struct that contains signatures and public keys necessary for
47/// authenticating a MultiSig.
48#[serde_as]
49#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)]
50pub struct MultiSig {
51    /// The plain signature encoded with signature scheme.
52    sigs: Vec<CompressedSignature>,
53    /// A bitmap that indicates the position of which public key the signature
54    /// should be authenticated with.
55    bitmap: BitmapUnit,
56    /// The public key encoded with each public key with its signature scheme
57    /// used along with the corresponding weight.
58    multisig_pk: MultiSigPublicKey,
59    /// A bytes representation of [struct MultiSig]. This helps with
60    /// implementing [trait AsRef<[u8]>].
61    #[serde(skip)]
62    bytes: OnceCell<Vec<u8>>,
63}
64
65/// Necessary trait for [struct SenderSignedData].
66impl PartialEq for MultiSig {
67    fn eq(&self, other: &Self) -> bool {
68        self.sigs == other.sigs
69            && self.bitmap == other.bitmap
70            && self.multisig_pk == other.multisig_pk
71    }
72}
73
74/// Necessary trait for [struct SenderSignedData].
75impl Eq for MultiSig {}
76
77/// Necessary trait for [struct SenderSignedData].
78impl Hash for MultiSig {
79    fn hash<H: Hasher>(&self, state: &mut H) {
80        self.as_ref().hash(state);
81    }
82}
83
84impl AuthenticatorTrait for MultiSig {
85    fn verify_user_authenticator_epoch(
86        &self,
87        epoch_id: EpochId,
88        max_epoch_upper_bound_delta: Option<u64>,
89    ) -> Result<(), IotaError> {
90        // If there is any zkLogin signatures, filter and check epoch for each.
91        // TODO: call this on all sigs to avoid future lapses
92        self.get_zklogin_sigs()?.iter().try_for_each(|s| {
93            s.verify_user_authenticator_epoch(epoch_id, max_epoch_upper_bound_delta)
94        })
95    }
96
97    fn verify_claims<T>(
98        &self,
99        value: &IntentMessage<T>,
100        multisig_address: IotaAddress,
101        verify_params: &VerifyParams,
102        zklogin_inputs_cache: Arc<VerifiedDigestCache<ZKLoginInputsDigest>>,
103    ) -> Result<(), IotaError>
104    where
105        T: Serialize,
106    {
107        self.multisig_pk
108            .validate()
109            .map_err(|_| IotaError::InvalidSignature {
110                error: "Invalid multisig pubkey".to_string(),
111            })?;
112
113        if IotaAddress::from(&self.multisig_pk) != multisig_address {
114            return Err(IotaError::InvalidSignature {
115                error: "Invalid address derived from pks".to_string(),
116            });
117        }
118
119        if !self.get_zklogin_sigs()?.is_empty() && !verify_params.accept_zklogin_in_multisig {
120            return Err(IotaError::InvalidSignature {
121                error: "zkLogin sig not supported inside multisig".to_string(),
122            });
123        }
124
125        let mut weight_sum: u16 = 0;
126        let message = bcs::to_bytes(&value).expect("Message serialization should not fail");
127        let mut hasher = DefaultHash::default();
128        hasher.update(message);
129        let digest = hasher.finalize().digest;
130        // Verify each signature against its corresponding signature scheme and public
131        // key. TODO: further optimization can be done because multiple Ed25519
132        // signatures can be batch verified.
133        for (sig, i) in self.sigs.iter().zip(as_indices(self.bitmap)?) {
134            let (subsig_pubkey, weight) =
135                self.multisig_pk
136                    .pk_map
137                    .get(i as usize)
138                    .ok_or(IotaError::InvalidSignature {
139                        error: "Invalid public keys index".to_string(),
140                    })?;
141            let res = match sig {
142                CompressedSignature::Ed25519(s) => {
143                    let pk =
144                        Ed25519PublicKey::from_bytes(subsig_pubkey.as_ref()).map_err(|_| {
145                            IotaError::InvalidSignature {
146                                error: "Invalid ed25519 pk bytes".to_string(),
147                            }
148                        })?;
149                    pk.verify(
150                        &digest,
151                        &s.try_into().map_err(|_| IotaError::InvalidSignature {
152                            error: "Invalid ed25519 signature bytes".to_string(),
153                        })?,
154                    )
155                }
156                CompressedSignature::Secp256k1(s) => {
157                    let pk =
158                        Secp256k1PublicKey::from_bytes(subsig_pubkey.as_ref()).map_err(|_| {
159                            IotaError::InvalidSignature {
160                                error: "Invalid k1 pk bytes".to_string(),
161                            }
162                        })?;
163                    pk.verify(
164                        &digest,
165                        &s.try_into().map_err(|_| IotaError::InvalidSignature {
166                            error: "Invalid k1 signature bytes".to_string(),
167                        })?,
168                    )
169                }
170                CompressedSignature::Secp256r1(s) => {
171                    let pk =
172                        Secp256r1PublicKey::from_bytes(subsig_pubkey.as_ref()).map_err(|_| {
173                            IotaError::InvalidSignature {
174                                error: "Invalid r1 pk bytes".to_string(),
175                            }
176                        })?;
177                    pk.verify(
178                        &digest,
179                        &s.try_into().map_err(|_| IotaError::InvalidSignature {
180                            error: "Invalid r1 signature bytes".to_string(),
181                        })?,
182                    )
183                }
184                CompressedSignature::ZkLogin(z) => {
185                    let authenticator = ZkLoginAuthenticator::from_bytes(&z.0).map_err(|_| {
186                        IotaError::InvalidSignature {
187                            error: "Invalid zklogin authenticator bytes".to_string(),
188                        }
189                    })?;
190                    authenticator
191                        .verify_claims(
192                            value,
193                            IotaAddress::from(subsig_pubkey),
194                            verify_params,
195                            zklogin_inputs_cache.clone(),
196                        )
197                        .map_err(|e| FastCryptoError::GeneralError(e.to_string()))
198                }
199            };
200            if res.is_ok() {
201                weight_sum += *weight as u16;
202            } else {
203                return res.map_err(|e| IotaError::InvalidSignature {
204                    error: format!(
205                        "Invalid sig for pk={} address={:?} error={:?}",
206                        subsig_pubkey.encode_base64(),
207                        IotaAddress::from(subsig_pubkey),
208                        e.to_string()
209                    ),
210                });
211            }
212        }
213        if weight_sum >= self.multisig_pk.threshold {
214            Ok(())
215        } else {
216            Err(IotaError::InvalidSignature {
217                error: format!(
218                    "Insufficient weight={:?} threshold={:?}",
219                    weight_sum, self.multisig_pk.threshold
220                ),
221            })
222        }
223    }
224}
225
226/// Interpret a bitmap of 01s as a list of indices that is set to 1s.
227/// e.g. 22 = 0b10110, then the result is [1, 2, 4].
228pub fn as_indices(bitmap: u16) -> Result<Vec<u8>, IotaError> {
229    if bitmap > MAX_BITMAP_VALUE {
230        return Err(IotaError::InvalidSignature {
231            error: "Invalid bitmap".to_string(),
232        });
233    }
234    let mut res = Vec::new();
235    for i in 0..10 {
236        if bitmap & (1 << i) != 0 {
237            res.push(i as u8);
238        }
239    }
240    Ok(res)
241}
242
243impl MultiSig {
244    /// Create MultiSig from its fields without validation
245    pub fn insecure_new(
246        sigs: Vec<CompressedSignature>,
247        bitmap: BitmapUnit,
248        multisig_pk: MultiSigPublicKey,
249    ) -> Self {
250        Self {
251            sigs,
252            bitmap,
253            multisig_pk,
254            bytes: OnceCell::new(),
255        }
256    }
257    /// This combines a list of [enum Signature] `flag || signature || pk` to a
258    /// MultiSig. The order of full_sigs must be the same as the order of
259    /// public keys in [enum MultiSigPublicKey]. e.g. for [pk1, pk2, pk3,
260    /// pk4, pk5], [sig1, sig2, sig5] is valid, but [sig2, sig1, sig5] is
261    /// invalid.
262    pub fn combine(
263        full_sigs: Vec<GenericSignature>,
264        multisig_pk: MultiSigPublicKey,
265    ) -> Result<Self, IotaError> {
266        multisig_pk
267            .validate()
268            .map_err(|_| IotaError::InvalidSignature {
269                error: "Invalid multisig public key".to_string(),
270            })?;
271
272        if full_sigs.len() > multisig_pk.pk_map.len() || full_sigs.is_empty() {
273            return Err(IotaError::InvalidSignature {
274                error: "Invalid number of signatures".to_string(),
275            });
276        }
277        let mut bitmap = 0;
278        let mut sigs = Vec::with_capacity(full_sigs.len());
279        for s in full_sigs {
280            let pk = s.to_public_key()?;
281            let index = multisig_pk
282                .get_index(&pk)
283                .ok_or(IotaError::IncorrectSigner {
284                    error: format!("pk does not exist: {:?}", pk),
285                })?;
286            if bitmap & (1 << index) != 0 {
287                return Err(IotaError::InvalidSignature {
288                    error: "Duplicate public key".to_string(),
289                });
290            }
291            bitmap |= 1 << index;
292            sigs.push(s.to_compressed()?);
293        }
294
295        Ok(MultiSig {
296            sigs,
297            bitmap,
298            multisig_pk,
299            bytes: OnceCell::new(),
300        })
301    }
302
303    pub fn init_and_validate(&mut self) -> Result<Self, FastCryptoError> {
304        if self.sigs.len() > self.multisig_pk.pk_map.len()
305            || self.sigs.is_empty()
306            || self.bitmap > MAX_BITMAP_VALUE
307        {
308            return Err(FastCryptoError::InvalidInput);
309        }
310        self.multisig_pk.validate()?;
311        Ok(self.to_owned())
312    }
313
314    pub fn get_pk(&self) -> &MultiSigPublicKey {
315        &self.multisig_pk
316    }
317
318    pub fn get_sigs(&self) -> &[CompressedSignature] {
319        &self.sigs
320    }
321
322    pub fn get_zklogin_sigs(&self) -> Result<Vec<ZkLoginAuthenticator>, IotaError> {
323        let authenticator_as_bytes: Vec<_> = self
324            .sigs
325            .iter()
326            .filter_map(|s| match s {
327                CompressedSignature::ZkLogin(z) => Some(z),
328                _ => None,
329            })
330            .collect();
331        authenticator_as_bytes
332            .iter()
333            .map(|z| {
334                ZkLoginAuthenticator::from_bytes(&z.0).map_err(|_| IotaError::InvalidSignature {
335                    error: "Invalid zklogin authenticator bytes".to_string(),
336                })
337            })
338            .collect()
339    }
340
341    pub fn get_indices(&self) -> Result<Vec<u8>, IotaError> {
342        as_indices(self.bitmap)
343    }
344}
345
346impl ToFromBytes for MultiSig {
347    fn from_bytes(bytes: &[u8]) -> Result<MultiSig, FastCryptoError> {
348        // The first byte matches the flag of MultiSig.
349        if bytes.first().ok_or(FastCryptoError::InvalidInput)? != &SignatureScheme::MultiSig.flag()
350        {
351            return Err(FastCryptoError::InvalidInput);
352        }
353        let mut multisig: MultiSig =
354            bcs::from_bytes(&bytes[1..]).map_err(|_| FastCryptoError::InvalidSignature)?;
355        multisig.init_and_validate()
356    }
357}
358
359impl FromStr for MultiSig {
360    type Err = IotaError;
361
362    fn from_str(s: &str) -> Result<Self, Self::Err> {
363        let bytes = Base64::decode(s).map_err(|_| IotaError::InvalidSignature {
364            error: "Invalid base64 string".to_string(),
365        })?;
366        let sig = MultiSig::from_bytes(&bytes).map_err(|_| IotaError::InvalidSignature {
367            error: "Invalid multisig bytes".to_string(),
368        })?;
369        Ok(sig)
370    }
371}
372
373/// This initialize the underlying bytes representation of MultiSig. It encodes
374/// [struct MultiSig] as the MultiSig flag (0x03) concat with the bcs bytes
375/// of [struct MultiSig] i.e. `flag || bcs_bytes(MultiSig)`.
376impl AsRef<[u8]> for MultiSig {
377    fn as_ref(&self) -> &[u8] {
378        self.bytes
379            .get_or_try_init::<_, eyre::Report>(|| {
380                let as_bytes = bcs::to_bytes(self).expect("BCS serialization should not fail");
381                let mut bytes = Vec::with_capacity(1 + as_bytes.len());
382                bytes.push(SignatureScheme::MultiSig.flag());
383                bytes.extend_from_slice(as_bytes.as_slice());
384                Ok(bytes)
385            })
386            .expect("OnceCell invariant violated")
387    }
388}
389
390/// The struct that contains the public key used for authenticating a MultiSig.
391#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
392pub struct MultiSigPublicKey {
393    /// A list of public key and its corresponding weight.
394    pk_map: Vec<(PublicKey, WeightUnit)>,
395    /// If the total weight of the public keys corresponding to verified
396    /// signatures is larger than threshold, the MultiSig is verified.
397    threshold: ThresholdUnit,
398}
399
400impl MultiSigPublicKey {
401    /// Construct MultiSigPublicKey without validation.
402    pub fn insecure_new(pk_map: Vec<(PublicKey, WeightUnit)>, threshold: ThresholdUnit) -> Self {
403        Self { pk_map, threshold }
404    }
405
406    pub fn new(
407        pks: Vec<PublicKey>,
408        weights: Vec<WeightUnit>,
409        threshold: ThresholdUnit,
410    ) -> Result<Self, IotaError> {
411        if pks.is_empty()
412            || weights.is_empty()
413            || threshold == 0
414            || pks.len() != weights.len()
415            || pks.len() > MAX_SIGNER_IN_MULTISIG
416            || weights.contains(&0)
417            || weights
418                .iter()
419                .map(|w| *w as ThresholdUnit)
420                .sum::<ThresholdUnit>()
421                < threshold
422            || pks
423                .iter()
424                .enumerate()
425                .any(|(i, pk)| pks.iter().skip(i + 1).any(|other_pk| *pk == *other_pk))
426        {
427            return Err(IotaError::InvalidSignature {
428                error: "Invalid multisig public key construction".to_string(),
429            });
430        }
431
432        Ok(MultiSigPublicKey {
433            pk_map: pks.into_iter().zip(weights).collect(),
434            threshold,
435        })
436    }
437
438    pub fn get_index(&self, pk: &PublicKey) -> Option<u8> {
439        self.pk_map.iter().position(|x| &x.0 == pk).map(|x| x as u8)
440    }
441
442    pub fn threshold(&self) -> &ThresholdUnit {
443        &self.threshold
444    }
445
446    pub fn pubkeys(&self) -> &Vec<(PublicKey, WeightUnit)> {
447        &self.pk_map
448    }
449
450    pub fn validate(&self) -> Result<MultiSigPublicKey, FastCryptoError> {
451        let pk_map = self.pubkeys();
452        if self.threshold == 0
453            || pk_map.is_empty()
454            || pk_map.len() > MAX_SIGNER_IN_MULTISIG
455            || pk_map.iter().any(|(_pk, weight)| *weight == 0)
456            || pk_map
457                .iter()
458                .map(|(_pk, weight)| *weight as ThresholdUnit)
459                .sum::<ThresholdUnit>()
460                < self.threshold
461            || pk_map.iter().enumerate().any(|(i, (pk, _weight))| {
462                pk_map
463                    .iter()
464                    .skip(i + 1)
465                    .any(|(other_pk, _weight)| *pk == *other_pk)
466            })
467        {
468            return Err(FastCryptoError::InvalidInput);
469        }
470        Ok(self.to_owned())
471    }
472}