sui_types/
multisig_legacy.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{
5    crypto::{CompressedSignature, SignatureScheme},
6    digests::ZKLoginInputsDigest,
7    error::SuiErrorKind,
8    multisig::{MultiSig, MultiSigPublicKey},
9    signature::{AuthenticatorTrait, GenericSignature, VerifyParams},
10    signature_verification::VerifiedDigestCache,
11    sui_serde::SuiBitmap,
12};
13pub use enum_dispatch::enum_dispatch;
14use fastcrypto::{
15    encoding::Base64,
16    error::FastCryptoError,
17    traits::{EncodeDecodeBase64, ToFromBytes},
18};
19use once_cell::sync::OnceCell;
20use roaring::RoaringBitmap;
21use schemars::JsonSchema;
22use serde::{Deserialize, Deserializer, Serialize, Serializer, ser::SerializeSeq};
23use serde_with::serde_as;
24use shared_crypto::intent::IntentMessage;
25use std::{
26    hash::{Hash, Hasher},
27    sync::Arc,
28};
29
30use crate::{
31    base_types::{EpochId, SuiAddress},
32    crypto::PublicKey,
33    error::SuiError,
34};
35
36pub type WeightUnit = u8;
37pub type ThresholdUnit = u16;
38pub const MAX_SIGNER_IN_MULTISIG: usize = 10;
39
40/// Deprecated, use [struct MultiSig] instead.
41/// The struct that contains signatures and public keys necessary for authenticating a MultiSigLegacy.
42#[serde_as]
43#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)]
44pub struct MultiSigLegacy {
45    /// The plain signature encoded with signature scheme.
46    sigs: Vec<CompressedSignature>,
47    /// A bitmap that indicates the position of which public key the signature should be authenticated with.
48    #[schemars(with = "Base64")]
49    #[serde_as(as = "SuiBitmap")]
50    bitmap: RoaringBitmap,
51    /// The public key encoded with each public key with its signature scheme used along with the corresponding weight.
52    multisig_pk: MultiSigPublicKeyLegacy,
53    /// A bytes representation of [struct MultiSigLegacy]. This helps with implementing [trait AsRef<[u8]>].
54    #[serde(skip)]
55    bytes: OnceCell<Vec<u8>>,
56}
57
58/// This initialize the underlying bytes representation of MultiSig. It encodes
59/// [struct MultiSigLegacy] as the MultiSig flag (0x03) concat with the bcs bytes
60/// of [struct MultiSigLegacy] i.e. `flag || bcs_bytes(MultiSig)`.
61impl AsRef<[u8]> for MultiSigLegacy {
62    fn as_ref(&self) -> &[u8] {
63        self.bytes
64            .get_or_try_init::<_, eyre::Report>(|| {
65                let as_bytes = bcs::to_bytes(self).expect("BCS serialization should not fail");
66                let mut bytes = Vec::with_capacity(1 + as_bytes.len());
67                bytes.push(SignatureScheme::MultiSig.flag());
68                bytes.extend_from_slice(as_bytes.as_slice());
69                Ok(bytes)
70            })
71            .expect("OnceCell invariant violated")
72    }
73}
74
75/// Necessary trait for [struct SenderSignedData].
76impl PartialEq for MultiSigLegacy {
77    fn eq(&self, other: &Self) -> bool {
78        self.sigs == other.sigs
79            && self.bitmap == other.bitmap
80            && self.multisig_pk == other.multisig_pk
81    }
82}
83
84/// Necessary trait for [struct SenderSignedData].
85impl Eq for MultiSigLegacy {}
86
87/// Necessary trait for [struct SenderSignedData].
88impl Hash for MultiSigLegacy {
89    fn hash<H: Hasher>(&self, state: &mut H) {
90        self.as_ref().hash(state);
91    }
92}
93
94impl AuthenticatorTrait for MultiSigLegacy {
95    fn verify_user_authenticator_epoch(
96        &self,
97        epoch_id: EpochId,
98        max_epoch_upper_bound_delta: Option<u64>,
99    ) -> Result<(), SuiError> {
100        let multisig: MultiSig =
101            self.clone()
102                .try_into()
103                .map_err(|_| SuiErrorKind::InvalidSignature {
104                    error: "Invalid legacy multisig".to_string(),
105                })?;
106        multisig.verify_user_authenticator_epoch(epoch_id, max_epoch_upper_bound_delta)
107    }
108
109    fn verify_claims<T>(
110        &self,
111        value: &IntentMessage<T>,
112        author: SuiAddress,
113        aux_verify_data: &VerifyParams,
114        zklogin_inputs_cache: Arc<VerifiedDigestCache<ZKLoginInputsDigest>>,
115    ) -> Result<(), SuiError>
116    where
117        T: Serialize,
118    {
119        let multisig: MultiSig =
120            self.clone()
121                .try_into()
122                .map_err(|_| SuiErrorKind::InvalidSignature {
123                    error: "Invalid legacy multisig".to_string(),
124                })?;
125        multisig.verify_claims(value, author, aux_verify_data, zklogin_inputs_cache)
126    }
127}
128
129impl TryFrom<MultiSigLegacy> for MultiSig {
130    type Error = FastCryptoError;
131
132    fn try_from(multisig: MultiSigLegacy) -> Result<Self, Self::Error> {
133        MultiSig::insecure_new(
134            multisig.clone().sigs,
135            bitmap_to_u16(multisig.clone().bitmap)?,
136            multisig.multisig_pk.try_into()?,
137        )
138        .init_and_validate()
139    }
140}
141
142impl TryFrom<MultiSigPublicKeyLegacy> for MultiSigPublicKey {
143    type Error = FastCryptoError;
144    fn try_from(multisig: MultiSigPublicKeyLegacy) -> Result<Self, Self::Error> {
145        let multisig_pk_legacy =
146            MultiSigPublicKey::insecure_new(multisig.pk_map, multisig.threshold).validate()?;
147        Ok(multisig_pk_legacy)
148    }
149}
150
151/// Convert a roaring bitmap to plain bitmap.
152pub fn bitmap_to_u16(roaring: RoaringBitmap) -> Result<u16, FastCryptoError> {
153    let mut val = 0;
154    for i in roaring {
155        if i >= 10 {
156            return Err(FastCryptoError::InvalidInput);
157        }
158        val |= 1 << i as u8;
159    }
160    Ok(val)
161}
162
163impl MultiSigLegacy {
164    /// This combines a list of [enum Signature] `flag || signature || pk` to a MultiSig.
165    pub fn combine(
166        full_sigs: Vec<GenericSignature>,
167        multisig_pk: MultiSigPublicKeyLegacy,
168    ) -> Result<Self, SuiError> {
169        multisig_pk
170            .validate()
171            .map_err(|_| SuiErrorKind::InvalidSignature {
172                error: "Invalid multisig public key".to_string(),
173            })?;
174
175        if full_sigs.len() > multisig_pk.pk_map.len() || full_sigs.is_empty() {
176            return Err(SuiErrorKind::InvalidSignature {
177                error: "Invalid number of signatures".to_string(),
178            }
179            .into());
180        }
181        let mut bitmap = RoaringBitmap::new();
182        let mut sigs = Vec::with_capacity(full_sigs.len());
183        for s in full_sigs {
184            let pk = s.to_public_key()?;
185            let inserted = bitmap.insert(multisig_pk.get_index(&pk).ok_or(
186                SuiErrorKind::IncorrectSigner {
187                    error: format!("pk does not exist: {:?}", pk),
188                },
189            )?);
190            if !inserted {
191                return Err(SuiErrorKind::InvalidSignature {
192                    error: "Duplicate signature".to_string(),
193                }
194                .into());
195            }
196            sigs.push(s.to_compressed()?);
197        }
198        Ok(MultiSigLegacy {
199            sigs,
200            bitmap,
201            multisig_pk,
202            bytes: OnceCell::new(),
203        })
204    }
205
206    pub fn validate(&self) -> Result<(), FastCryptoError> {
207        if bitmap_to_u16(self.bitmap.clone()).is_err()
208            || self.sigs.len() > self.multisig_pk.pk_map.len()
209            || self.sigs.is_empty()
210        {
211            return Err(FastCryptoError::InvalidInput);
212        }
213        self.multisig_pk.validate()?;
214        Ok(())
215    }
216
217    pub fn get_pk(&self) -> &MultiSigPublicKeyLegacy {
218        &self.multisig_pk
219    }
220
221    pub fn get_sigs(&self) -> &[CompressedSignature] {
222        &self.sigs
223    }
224
225    pub fn get_bitmap(&self) -> &RoaringBitmap {
226        &self.bitmap
227    }
228}
229
230impl ToFromBytes for MultiSigLegacy {
231    fn from_bytes(bytes: &[u8]) -> Result<MultiSigLegacy, FastCryptoError> {
232        // The first byte matches the flag of MultiSig.
233        if bytes.first().ok_or(FastCryptoError::InvalidInput)? != &SignatureScheme::MultiSig.flag()
234        {
235            return Err(FastCryptoError::InvalidInput);
236        }
237        let multisig: MultiSigLegacy =
238            bcs::from_bytes(&bytes[1..]).map_err(|_| FastCryptoError::InvalidInput)?;
239        multisig.validate()?;
240        Ok(multisig)
241    }
242}
243
244/// Deprecated, use [struct MultiSigPublicKey] instead.
245/// The struct that contains the public key used for authenticating a MultiSig.
246#[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Serialize, Deserialize)]
247pub struct MultiSigPublicKeyLegacy {
248    /// A list of public key and its corresponding weight.
249    #[serde(serialize_with = "serialize_pk_map")]
250    #[serde(deserialize_with = "deserialize_pk_map")]
251    pk_map: Vec<(PublicKey, WeightUnit)>,
252    /// If the total weight of the public keys corresponding to verified signatures is larger than threshold, the MultiSig is verified.
253    threshold: ThresholdUnit,
254}
255
256/// Legacy serialization for MultiSigPublicKey where PublicKey is serialized as string in base64 encoding.
257fn serialize_pk_map<S>(pk_map: &[(PublicKey, WeightUnit)], serializer: S) -> Result<S::Ok, S::Error>
258where
259    S: Serializer,
260{
261    let pk_weight_arr: Vec<(String, WeightUnit)> = pk_map
262        .iter()
263        .map(|(pk, w)| (pk.encode_base64(), *w))
264        .collect();
265
266    let mut seq = serializer.serialize_seq(Some(pk_weight_arr.len()))?;
267    for (pk_string, w) in pk_weight_arr {
268        seq.serialize_element(&(pk_string, w))?;
269    }
270    seq.end()
271}
272
273/// Legacy deserialization for MultiSigPublicKey where PublicKey is deserialized from base64 encoded string.
274fn deserialize_pk_map<'de, D>(deserializer: D) -> Result<Vec<(PublicKey, WeightUnit)>, D::Error>
275where
276    D: Deserializer<'de>,
277{
278    use serde::de::Error;
279    let pk_weight_arr: Vec<(String, WeightUnit)> = Vec::deserialize(deserializer)?;
280    pk_weight_arr
281        .into_iter()
282        .map(|(s, w)| {
283            let pk = <PublicKey as EncodeDecodeBase64>::decode_base64(&s)
284                .map_err(|e| Error::custom(e.to_string()))?;
285            Ok((pk, w))
286        })
287        .collect()
288}
289
290impl MultiSigPublicKeyLegacy {
291    pub fn new(
292        pks: Vec<PublicKey>,
293        weights: Vec<WeightUnit>,
294        threshold: ThresholdUnit,
295    ) -> Result<Self, SuiError> {
296        if pks.is_empty()
297            || weights.is_empty()
298            || threshold == 0
299            || pks.len() != weights.len()
300            || pks.len() > MAX_SIGNER_IN_MULTISIG
301            || weights.contains(&0)
302            || weights
303                .iter()
304                .map(|w| *w as ThresholdUnit)
305                .sum::<ThresholdUnit>()
306                < threshold
307        {
308            return Err(SuiErrorKind::InvalidSignature {
309                error: "Invalid multisig public key construction".to_string(),
310            }
311            .into());
312        }
313        Ok(MultiSigPublicKeyLegacy {
314            pk_map: pks.into_iter().zip(weights).collect(),
315            threshold,
316        })
317    }
318
319    pub fn get_index(&self, pk: &PublicKey) -> Option<u32> {
320        self.pk_map
321            .iter()
322            .position(|x| &x.0 == pk)
323            .map(|x| x as u32)
324    }
325
326    pub fn threshold(&self) -> &ThresholdUnit {
327        &self.threshold
328    }
329
330    pub fn pubkeys(&self) -> &[(PublicKey, WeightUnit)] {
331        &self.pk_map
332    }
333
334    pub fn validate(&self) -> Result<Self, FastCryptoError> {
335        let multisig: MultiSigPublicKey = self.clone().try_into()?;
336        multisig.validate()?;
337        Ok(self.clone())
338    }
339}