1use 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 mysten_common::ZipDebugEqIteratorExt;
20use once_cell::sync::OnceCell;
21use roaring::RoaringBitmap;
22use schemars::JsonSchema;
23use serde::{Deserialize, Deserializer, Serialize, Serializer, ser::SerializeSeq};
24use serde_with::serde_as;
25use shared_crypto::intent::IntentMessage;
26use std::{
27 hash::{Hash, Hasher},
28 sync::Arc,
29};
30
31use crate::{
32 base_types::{EpochId, SuiAddress},
33 crypto::PublicKey,
34 error::SuiError,
35};
36
37pub type WeightUnit = u8;
38pub type ThresholdUnit = u16;
39pub const MAX_SIGNER_IN_MULTISIG: usize = 10;
40
41#[serde_as]
44#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)]
45pub struct MultiSigLegacy {
46 sigs: Vec<CompressedSignature>,
48 #[schemars(with = "Base64")]
50 #[serde_as(as = "SuiBitmap")]
51 bitmap: RoaringBitmap,
52 multisig_pk: MultiSigPublicKeyLegacy,
54 #[serde(skip)]
56 bytes: OnceCell<Vec<u8>>,
57}
58
59impl AsRef<[u8]> for MultiSigLegacy {
63 fn as_ref(&self) -> &[u8] {
64 self.bytes
65 .get_or_try_init::<_, eyre::Report>(|| {
66 let as_bytes = bcs::to_bytes(self).expect("BCS serialization should not fail");
67 let mut bytes = Vec::with_capacity(1 + as_bytes.len());
68 bytes.push(SignatureScheme::MultiSig.flag());
69 bytes.extend_from_slice(as_bytes.as_slice());
70 Ok(bytes)
71 })
72 .expect("OnceCell invariant violated")
73 }
74}
75
76impl PartialEq for MultiSigLegacy {
78 fn eq(&self, other: &Self) -> bool {
79 self.sigs == other.sigs
80 && self.bitmap == other.bitmap
81 && self.multisig_pk == other.multisig_pk
82 }
83}
84
85impl Eq for MultiSigLegacy {}
87
88impl Hash for MultiSigLegacy {
90 fn hash<H: Hasher>(&self, state: &mut H) {
91 self.as_ref().hash(state);
92 }
93}
94
95impl AuthenticatorTrait for MultiSigLegacy {
96 fn verify_user_authenticator_epoch(
97 &self,
98 epoch_id: EpochId,
99 max_epoch_upper_bound_delta: Option<u64>,
100 ) -> Result<(), SuiError> {
101 let multisig: MultiSig =
102 self.clone()
103 .try_into()
104 .map_err(|_| SuiErrorKind::InvalidSignature {
105 error: "Invalid legacy multisig".to_string(),
106 })?;
107 multisig.verify_user_authenticator_epoch(epoch_id, max_epoch_upper_bound_delta)
108 }
109
110 fn verify_claims<T>(
111 &self,
112 value: &IntentMessage<T>,
113 author: SuiAddress,
114 aux_verify_data: &VerifyParams,
115 zklogin_inputs_cache: Arc<VerifiedDigestCache<ZKLoginInputsDigest>>,
116 ) -> Result<(), SuiError>
117 where
118 T: Serialize,
119 {
120 let multisig: MultiSig =
121 self.clone()
122 .try_into()
123 .map_err(|_| SuiErrorKind::InvalidSignature {
124 error: "Invalid legacy multisig".to_string(),
125 })?;
126 multisig.verify_claims(value, author, aux_verify_data, zklogin_inputs_cache)
127 }
128}
129
130impl TryFrom<MultiSigLegacy> for MultiSig {
131 type Error = FastCryptoError;
132
133 fn try_from(multisig: MultiSigLegacy) -> Result<Self, Self::Error> {
134 MultiSig::insecure_new(
135 multisig.clone().sigs,
136 bitmap_to_u16(multisig.clone().bitmap)?,
137 multisig.multisig_pk.try_into()?,
138 )
139 .init_and_validate()
140 }
141}
142
143impl TryFrom<MultiSigPublicKeyLegacy> for MultiSigPublicKey {
144 type Error = FastCryptoError;
145 fn try_from(multisig: MultiSigPublicKeyLegacy) -> Result<Self, Self::Error> {
146 let multisig_pk_legacy =
147 MultiSigPublicKey::insecure_new(multisig.pk_map, multisig.threshold).validate()?;
148 Ok(multisig_pk_legacy)
149 }
150}
151
152pub fn bitmap_to_u16(roaring: RoaringBitmap) -> Result<u16, FastCryptoError> {
154 let mut val = 0;
155 for i in roaring {
156 if i >= 10 {
157 return Err(FastCryptoError::InvalidInput);
158 }
159 val |= 1 << i as u8;
160 }
161 Ok(val)
162}
163
164impl MultiSigLegacy {
165 pub fn combine(
167 full_sigs: Vec<GenericSignature>,
168 multisig_pk: MultiSigPublicKeyLegacy,
169 ) -> Result<Self, SuiError> {
170 multisig_pk
171 .validate()
172 .map_err(|_| SuiErrorKind::InvalidSignature {
173 error: "Invalid multisig public key".to_string(),
174 })?;
175
176 if full_sigs.len() > multisig_pk.pk_map.len() || full_sigs.is_empty() {
177 return Err(SuiErrorKind::InvalidSignature {
178 error: "Invalid number of signatures".to_string(),
179 }
180 .into());
181 }
182 let mut bitmap = RoaringBitmap::new();
183 let mut sigs = Vec::with_capacity(full_sigs.len());
184 for s in full_sigs {
185 let pk = s.to_public_key()?;
186 let inserted = bitmap.insert(multisig_pk.get_index(&pk).ok_or(
187 SuiErrorKind::IncorrectSigner {
188 error: format!("pk does not exist: {:?}", pk),
189 },
190 )?);
191 if !inserted {
192 return Err(SuiErrorKind::InvalidSignature {
193 error: "Duplicate signature".to_string(),
194 }
195 .into());
196 }
197 sigs.push(s.to_compressed()?);
198 }
199 Ok(MultiSigLegacy {
200 sigs,
201 bitmap,
202 multisig_pk,
203 bytes: OnceCell::new(),
204 })
205 }
206
207 pub fn validate(&self) -> Result<(), FastCryptoError> {
208 if bitmap_to_u16(self.bitmap.clone()).is_err()
209 || self.sigs.len() > self.multisig_pk.pk_map.len()
210 || self.sigs.is_empty()
211 {
212 return Err(FastCryptoError::InvalidInput);
213 }
214 self.multisig_pk.validate()?;
215 Ok(())
216 }
217
218 pub fn get_pk(&self) -> &MultiSigPublicKeyLegacy {
219 &self.multisig_pk
220 }
221
222 pub fn uses_mldsa65(&self) -> bool {
224 self.multisig_pk.has_mldsa65_member()
225 || self
226 .sigs
227 .iter()
228 .any(|s| matches!(s, CompressedSignature::MLDSA65(_)))
229 }
230
231 pub fn get_sigs(&self) -> &[CompressedSignature] {
232 &self.sigs
233 }
234
235 pub fn get_bitmap(&self) -> &RoaringBitmap {
236 &self.bitmap
237 }
238}
239
240impl ToFromBytes for MultiSigLegacy {
241 fn from_bytes(bytes: &[u8]) -> Result<MultiSigLegacy, FastCryptoError> {
242 if bytes.first().ok_or(FastCryptoError::InvalidInput)? != &SignatureScheme::MultiSig.flag()
244 {
245 return Err(FastCryptoError::InvalidInput);
246 }
247 let multisig: MultiSigLegacy =
248 bcs::from_bytes(&bytes[1..]).map_err(|_| FastCryptoError::InvalidInput)?;
249 multisig.validate()?;
250 Ok(multisig)
251 }
252}
253
254#[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Serialize, Deserialize)]
257pub struct MultiSigPublicKeyLegacy {
258 #[serde(serialize_with = "serialize_pk_map")]
260 #[serde(deserialize_with = "deserialize_pk_map")]
261 pk_map: Vec<(PublicKey, WeightUnit)>,
262 threshold: ThresholdUnit,
264}
265
266fn serialize_pk_map<S>(pk_map: &[(PublicKey, WeightUnit)], serializer: S) -> Result<S::Ok, S::Error>
268where
269 S: Serializer,
270{
271 let pk_weight_arr: Vec<(String, WeightUnit)> = pk_map
272 .iter()
273 .map(|(pk, w)| (pk.encode_base64(), *w))
274 .collect();
275
276 let mut seq = serializer.serialize_seq(Some(pk_weight_arr.len()))?;
277 for (pk_string, w) in pk_weight_arr {
278 seq.serialize_element(&(pk_string, w))?;
279 }
280 seq.end()
281}
282
283fn deserialize_pk_map<'de, D>(deserializer: D) -> Result<Vec<(PublicKey, WeightUnit)>, D::Error>
285where
286 D: Deserializer<'de>,
287{
288 use serde::de::Error;
289 let pk_weight_arr: Vec<(String, WeightUnit)> = Vec::deserialize(deserializer)?;
290 pk_weight_arr
291 .into_iter()
292 .map(|(s, w)| {
293 let pk = <PublicKey as EncodeDecodeBase64>::decode_base64(&s)
294 .map_err(|e| Error::custom(e.to_string()))?;
295 Ok((pk, w))
296 })
297 .collect()
298}
299
300impl MultiSigPublicKeyLegacy {
301 pub fn new(
302 pks: Vec<PublicKey>,
303 weights: Vec<WeightUnit>,
304 threshold: ThresholdUnit,
305 ) -> Result<Self, SuiError> {
306 if pks.is_empty()
307 || weights.is_empty()
308 || threshold == 0
309 || pks.len() != weights.len()
310 || pks.len() > MAX_SIGNER_IN_MULTISIG
311 || weights.contains(&0)
312 || weights
313 .iter()
314 .map(|w| *w as ThresholdUnit)
315 .sum::<ThresholdUnit>()
316 < threshold
317 {
318 return Err(SuiErrorKind::InvalidSignature {
319 error: "Invalid multisig public key construction".to_string(),
320 }
321 .into());
322 }
323 Ok(MultiSigPublicKeyLegacy {
324 pk_map: pks.into_iter().zip_debug_eq(weights).collect(),
325 threshold,
326 })
327 }
328
329 pub fn get_index(&self, pk: &PublicKey) -> Option<u32> {
330 self.pk_map
331 .iter()
332 .position(|x| &x.0 == pk)
333 .map(|x| x as u32)
334 }
335
336 pub fn threshold(&self) -> &ThresholdUnit {
337 &self.threshold
338 }
339
340 pub fn pubkeys(&self) -> &[(PublicKey, WeightUnit)] {
341 &self.pk_map
342 }
343
344 pub fn has_mldsa65_member(&self) -> bool {
345 self.pk_map
346 .iter()
347 .any(|(pk, _)| matches!(pk, PublicKey::MLDSA65(_)))
348 }
349
350 pub fn validate(&self) -> Result<Self, FastCryptoError> {
351 let multisig: MultiSigPublicKey = self.clone().try_into()?;
352 multisig.validate()?;
353 Ok(self.clone())
354 }
355}