1use crate::{
5 crypto::{CompressedSignature, DefaultHash, SignatureScheme},
6 digests::ZKLoginInputsDigest,
7 error::SuiErrorKind,
8 passkey_authenticator::PasskeyAuthenticator,
9 signature::{AuthenticatorTrait, GenericSignature, VerifyParams},
10 signature_verification::VerifiedDigestCache,
11 zk_login_authenticator::ZkLoginAuthenticator,
12};
13pub use enum_dispatch::enum_dispatch;
14use fastcrypto::{
15 ed25519::Ed25519PublicKey,
16 encoding::{Base64, Encoding},
17 error::FastCryptoError,
18 hash::HashFunction,
19 secp256k1::Secp256k1PublicKey,
20 secp256r1::Secp256r1PublicKey,
21 traits::{EncodeDecodeBase64, ToFromBytes, VerifyingKey},
22};
23use mysten_common::ZipDebugEqIteratorExt;
24use once_cell::sync::OnceCell;
25use schemars::JsonSchema;
26use serde::{Deserialize, Serialize};
27use serde_with::serde_as;
28use shared_crypto::intent::IntentMessage;
29use std::{
30 hash::{Hash, Hasher},
31 str::FromStr,
32 sync::Arc,
33};
34
35use crate::{
36 base_types::{EpochId, SuiAddress},
37 crypto::PublicKey,
38 error::SuiError,
39};
40
41#[cfg(test)]
42#[path = "unit_tests/multisig_tests.rs"]
43mod multisig_tests;
44
45pub type WeightUnit = u8;
46pub type ThresholdUnit = u16;
47pub type BitmapUnit = u16;
48pub const MAX_SIGNER_IN_MULTISIG: usize = 10;
49pub const MAX_BITMAP_VALUE: BitmapUnit = 0b1111111111;
50#[serde_as]
52#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)]
53pub struct MultiSig {
54 sigs: Vec<CompressedSignature>,
56 bitmap: BitmapUnit,
58 multisig_pk: MultiSigPublicKey,
60 #[serde(skip)]
62 bytes: OnceCell<Vec<u8>>,
63}
64
65impl 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
74impl Eq for MultiSig {}
76
77impl 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<(), SuiError> {
90 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: SuiAddress,
101 verify_params: &VerifyParams,
102 zklogin_inputs_cache: Arc<VerifiedDigestCache<ZKLoginInputsDigest>>,
103 ) -> Result<(), SuiError>
104 where
105 T: Serialize,
106 {
107 self.multisig_pk
108 .validate()
109 .map_err(|_| SuiErrorKind::InvalidSignature {
110 error: "Invalid multisig pubkey".to_string(),
111 })?;
112
113 if verify_params.validate_zklogin_public_identifier {
115 for (pk, _weight) in &self.multisig_pk.pk_map {
116 if let PublicKey::ZkLogin(zklogin_pk) = pk {
117 zklogin_pk
118 .validate()
119 .map_err(|e| SuiErrorKind::InvalidSignature {
120 error: format!("Invalid zkLogin pk in multisig: {}", e),
121 })?;
122 }
123 }
124 }
125
126 if SuiAddress::from(&self.multisig_pk) != multisig_address {
127 return Err(SuiErrorKind::InvalidSignature {
128 error: "Invalid address derived from pks".to_string(),
129 }
130 .into());
131 }
132
133 if self.has_zklogin_sigs() && !verify_params.accept_zklogin_in_multisig {
134 return Err(SuiErrorKind::InvalidSignature {
135 error: "zkLogin sig not supported inside multisig".to_string(),
136 }
137 .into());
138 }
139
140 if self.has_passkey_sigs() && !verify_params.accept_passkey_in_multisig {
141 return Err(SuiErrorKind::InvalidSignature {
142 error: "Passkey sig not supported inside multisig".to_string(),
143 }
144 .into());
145 }
146
147 let mut weight_sum: u16 = 0;
148 let mut hasher = DefaultHash::default();
149 bcs::serialize_into(&mut hasher, &value).expect("Message serialization should not fail");
150 let digest = hasher.finalize().digest;
151 for (sig, i) in self.sigs.iter().zip_debug_eq(as_indices(self.bitmap)?) {
154 let (subsig_pubkey, weight) =
155 self.multisig_pk
156 .pk_map
157 .get(i as usize)
158 .ok_or(SuiErrorKind::InvalidSignature {
159 error: "Invalid public keys index".to_string(),
160 })?;
161 let res = match sig {
162 CompressedSignature::Ed25519(s) => {
163 if verify_params.additional_multisig_checks
164 && !matches!(subsig_pubkey.scheme(), SignatureScheme::ED25519)
165 {
166 return Err(SuiErrorKind::InvalidSignature {
167 error: format!(
168 "Invalid sig for pk={} address={:?} error=signature/pubkey type mismatch",
169 subsig_pubkey.encode_base64(),
170 SuiAddress::from(subsig_pubkey)
171 ),
172 }.into());
173 }
174 let pk =
175 Ed25519PublicKey::from_bytes(subsig_pubkey.as_ref()).map_err(|_| {
176 SuiErrorKind::InvalidSignature {
177 error: "Invalid ed25519 pk bytes".to_string(),
178 }
179 })?;
180 pk.verify(
181 &digest,
182 &s.try_into().map_err(|_| SuiErrorKind::InvalidSignature {
183 error: "Invalid ed25519 signature bytes".to_string(),
184 })?,
185 )
186 }
187 CompressedSignature::Secp256k1(s) => {
188 if verify_params.additional_multisig_checks
189 && !matches!(subsig_pubkey.scheme(), SignatureScheme::Secp256k1)
190 {
191 return Err(SuiErrorKind::InvalidSignature {
192 error: format!(
193 "Invalid sig for pk={} address={:?} error=signature/pubkey type mismatch",
194 subsig_pubkey.encode_base64(),
195 SuiAddress::from(subsig_pubkey)
196 ),
197 }.into());
198 }
199 let pk =
200 Secp256k1PublicKey::from_bytes(subsig_pubkey.as_ref()).map_err(|_| {
201 SuiErrorKind::InvalidSignature {
202 error: "Invalid k1 pk bytes".to_string(),
203 }
204 })?;
205 pk.verify(
206 &digest,
207 &s.try_into().map_err(|_| SuiErrorKind::InvalidSignature {
208 error: "Invalid k1 signature bytes".to_string(),
209 })?,
210 )
211 }
212 CompressedSignature::Secp256r1(s) => {
213 if verify_params.additional_multisig_checks
214 && !matches!(subsig_pubkey.scheme(), SignatureScheme::Secp256r1)
215 {
216 return Err(SuiErrorKind::InvalidSignature {
217 error: format!(
218 "Invalid sig for pk={} address={:?} error=signature/pubkey type mismatch",
219 subsig_pubkey.encode_base64(),
220 SuiAddress::from(subsig_pubkey)
221 ),
222 }.into());
223 }
224 let pk =
225 Secp256r1PublicKey::from_bytes(subsig_pubkey.as_ref()).map_err(|_| {
226 SuiErrorKind::InvalidSignature {
227 error: "Invalid r1 pk bytes".to_string(),
228 }
229 })?;
230 pk.verify(
231 &digest,
232 &s.try_into().map_err(|_| SuiErrorKind::InvalidSignature {
233 error: "Invalid r1 signature bytes".to_string(),
234 })?,
235 )
236 }
237 CompressedSignature::ZkLogin(z) => {
238 if verify_params.additional_multisig_checks
239 && !matches!(
240 subsig_pubkey.scheme(),
241 SignatureScheme::ZkLoginAuthenticator
242 )
243 {
244 return Err(SuiErrorKind::InvalidSignature {
245 error: format!(
246 "Invalid sig for pk={} address={:?} error=signature/pubkey type mismatch",
247 subsig_pubkey.encode_base64(),
248 SuiAddress::from(subsig_pubkey)
249 ),
250 }.into());
251 }
252 let authenticator = ZkLoginAuthenticator::from_bytes(&z.0).map_err(|_| {
253 SuiErrorKind::InvalidSignature {
254 error: "Invalid zklogin authenticator bytes".to_string(),
255 }
256 })?;
257 authenticator
258 .verify_claims(
259 value,
260 SuiAddress::from(subsig_pubkey),
261 verify_params,
262 zklogin_inputs_cache.clone(),
263 )
264 .map_err(|e| FastCryptoError::GeneralError(e.to_string()))
265 }
266 CompressedSignature::Passkey(bytes) => {
267 let authenticator =
268 PasskeyAuthenticator::from_bytes(&bytes.0).map_err(|_| {
269 SuiErrorKind::InvalidSignature {
270 error: "Invalid passkey authenticator bytes".to_string(),
271 }
272 })?;
273 authenticator
274 .verify_claims(
275 value,
276 SuiAddress::from(subsig_pubkey),
277 verify_params,
278 zklogin_inputs_cache.clone(),
279 )
280 .map_err(|e| FastCryptoError::GeneralError(e.to_string()))
281 }
282 };
283 if res.is_ok() {
284 weight_sum += *weight as u16;
285 } else {
286 return res.map_err(|e| {
287 SuiErrorKind::InvalidSignature {
288 error: format!(
289 "Invalid sig for pk={} address={:?} error={:?}",
290 subsig_pubkey.encode_base64(),
291 SuiAddress::from(subsig_pubkey),
292 e.to_string()
293 ),
294 }
295 .into()
296 });
297 }
298 }
299 if weight_sum >= self.multisig_pk.threshold {
300 Ok(())
301 } else {
302 Err(SuiErrorKind::InvalidSignature {
303 error: format!(
304 "Insufficient weight={:?} threshold={:?}",
305 weight_sum, self.multisig_pk.threshold
306 ),
307 }
308 .into())
309 }
310 }
311}
312
313pub fn as_indices(bitmap: u16) -> Result<Vec<u8>, SuiError> {
316 if bitmap > MAX_BITMAP_VALUE {
317 return Err(SuiErrorKind::InvalidSignature {
318 error: "Invalid bitmap".to_string(),
319 }
320 .into());
321 }
322 let mut res = Vec::new();
323 for i in 0..10 {
324 if bitmap & (1 << i) != 0 {
325 res.push(i as u8);
326 }
327 }
328 Ok(res)
329}
330
331impl MultiSig {
332 pub fn insecure_new(
334 sigs: Vec<CompressedSignature>,
335 bitmap: BitmapUnit,
336 multisig_pk: MultiSigPublicKey,
337 ) -> Self {
338 Self {
339 sigs,
340 bitmap,
341 multisig_pk,
342 bytes: OnceCell::new(),
343 }
344 }
345 pub fn combine(
350 full_sigs: Vec<GenericSignature>,
351 multisig_pk: MultiSigPublicKey,
352 ) -> Result<Self, SuiError> {
353 multisig_pk
354 .validate()
355 .map_err(|_| SuiErrorKind::InvalidSignature {
356 error: "Invalid multisig public key".to_string(),
357 })?;
358
359 if full_sigs.len() > multisig_pk.pk_map.len() || full_sigs.is_empty() {
360 return Err(SuiErrorKind::InvalidSignature {
361 error: "Invalid number of signatures".to_string(),
362 }
363 .into());
364 }
365 let mut bitmap = 0;
366 let mut sigs = Vec::with_capacity(full_sigs.len());
367 for s in full_sigs {
368 let pk = s.to_public_key()?;
369 let index = multisig_pk
370 .get_index(&pk)
371 .ok_or(SuiErrorKind::IncorrectSigner {
372 error: format!("pk does not exist: {:?}", pk),
373 })?;
374 if bitmap & (1 << index) != 0 {
375 return Err(SuiErrorKind::InvalidSignature {
376 error: "Duplicate public key".to_string(),
377 }
378 .into());
379 }
380 bitmap |= 1 << index;
381 sigs.push(s.to_compressed()?);
382 }
383
384 Ok(MultiSig {
385 sigs,
386 bitmap,
387 multisig_pk,
388 bytes: OnceCell::new(),
389 })
390 }
391
392 pub fn init_and_validate(&mut self) -> Result<Self, FastCryptoError> {
393 if self.sigs.len() > self.multisig_pk.pk_map.len()
394 || self.sigs.is_empty()
395 || self.bitmap > MAX_BITMAP_VALUE
396 {
397 return Err(FastCryptoError::InvalidInput);
398 }
399 self.multisig_pk.validate()?;
400 Ok(self.to_owned())
401 }
402
403 pub fn get_pk(&self) -> &MultiSigPublicKey {
404 &self.multisig_pk
405 }
406
407 pub fn get_sigs(&self) -> &[CompressedSignature] {
408 &self.sigs
409 }
410
411 pub fn get_bitmap(&self) -> u16 {
412 self.bitmap
413 }
414
415 pub fn get_zklogin_sigs(&self) -> Result<Vec<ZkLoginAuthenticator>, SuiError> {
416 let authenticator_as_bytes: Vec<_> = self
417 .sigs
418 .iter()
419 .filter_map(|s| match s {
420 CompressedSignature::ZkLogin(z) => Some(z),
421 _ => None,
422 })
423 .collect();
424 authenticator_as_bytes
425 .iter()
426 .map(|z| {
427 ZkLoginAuthenticator::from_bytes(&z.0).map_err(|_| {
428 SuiErrorKind::InvalidSignature {
429 error: "Invalid zklogin authenticator bytes".to_string(),
430 }
431 .into()
432 })
433 })
434 .collect()
435 }
436
437 pub fn get_indices(&self) -> Result<Vec<u8>, SuiError> {
438 as_indices(self.bitmap)
439 }
440
441 pub fn has_passkey_sigs(&self) -> bool {
442 self.sigs
443 .iter()
444 .any(|s| matches!(s, CompressedSignature::Passkey(_)))
445 }
446
447 pub fn has_zklogin_sigs(&self) -> bool {
448 self.sigs
449 .iter()
450 .any(|s| matches!(s, CompressedSignature::ZkLogin(_)))
451 }
452}
453
454impl ToFromBytes for MultiSig {
455 fn from_bytes(bytes: &[u8]) -> Result<MultiSig, FastCryptoError> {
456 if bytes.first().ok_or(FastCryptoError::InvalidInput)? != &SignatureScheme::MultiSig.flag()
458 {
459 return Err(FastCryptoError::InvalidInput);
460 }
461 let mut multisig: MultiSig =
462 bcs::from_bytes(&bytes[1..]).map_err(|_| FastCryptoError::InvalidSignature)?;
463 multisig.init_and_validate()
464 }
465}
466
467impl FromStr for MultiSig {
468 type Err = SuiError;
469
470 fn from_str(s: &str) -> Result<Self, Self::Err> {
471 let bytes = Base64::decode(s).map_err(|_| SuiErrorKind::InvalidSignature {
472 error: "Invalid base64 string".to_string(),
473 })?;
474 let sig = MultiSig::from_bytes(&bytes).map_err(|_| SuiErrorKind::InvalidSignature {
475 error: "Invalid multisig bytes".to_string(),
476 })?;
477 Ok(sig)
478 }
479}
480
481impl AsRef<[u8]> for MultiSig {
485 fn as_ref(&self) -> &[u8] {
486 self.bytes
487 .get_or_try_init::<_, eyre::Report>(|| {
488 let as_bytes = bcs::to_bytes(self).expect("BCS serialization should not fail");
489 let mut bytes = Vec::with_capacity(1 + as_bytes.len());
490 bytes.push(SignatureScheme::MultiSig.flag());
491 bytes.extend_from_slice(as_bytes.as_slice());
492 Ok(bytes)
493 })
494 .expect("OnceCell invariant violated")
495 }
496}
497
498#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
500pub struct MultiSigPublicKey {
501 pk_map: Vec<(PublicKey, WeightUnit)>,
503 threshold: ThresholdUnit,
505}
506
507impl MultiSigPublicKey {
508 pub fn insecure_new(pk_map: Vec<(PublicKey, WeightUnit)>, threshold: ThresholdUnit) -> Self {
510 Self { pk_map, threshold }
511 }
512
513 pub fn new(
514 pks: Vec<PublicKey>,
515 weights: Vec<WeightUnit>,
516 threshold: ThresholdUnit,
517 ) -> Result<Self, SuiError> {
518 if pks.is_empty()
519 || weights.is_empty()
520 || threshold == 0
521 || pks.len() != weights.len()
522 || pks.len() > MAX_SIGNER_IN_MULTISIG
523 || weights.contains(&0)
524 || weights
525 .iter()
526 .map(|w| *w as ThresholdUnit)
527 .sum::<ThresholdUnit>()
528 < threshold
529 || pks
530 .iter()
531 .enumerate()
532 .any(|(i, pk)| pks.iter().skip(i + 1).any(|other_pk| *pk == *other_pk))
533 {
534 return Err(SuiErrorKind::InvalidSignature {
535 error: "Invalid multisig public key construction".to_string(),
536 }
537 .into());
538 }
539
540 Ok(MultiSigPublicKey {
541 pk_map: pks.into_iter().zip_debug_eq(weights).collect(),
542 threshold,
543 })
544 }
545
546 pub fn get_index(&self, pk: &PublicKey) -> Option<u8> {
547 self.pk_map.iter().position(|x| &x.0 == pk).map(|x| x as u8)
548 }
549
550 pub fn threshold(&self) -> &ThresholdUnit {
551 &self.threshold
552 }
553
554 pub fn pubkeys(&self) -> &Vec<(PublicKey, WeightUnit)> {
555 &self.pk_map
556 }
557
558 pub fn validate(&self) -> Result<MultiSigPublicKey, FastCryptoError> {
559 let pk_map = self.pubkeys();
560 if self.threshold == 0
561 || pk_map.is_empty()
562 || pk_map.len() > MAX_SIGNER_IN_MULTISIG
563 || pk_map.iter().any(|(_pk, weight)| *weight == 0)
564 || pk_map
565 .iter()
566 .map(|(_pk, weight)| *weight as ThresholdUnit)
567 .sum::<ThresholdUnit>()
568 < self.threshold
569 || pk_map.iter().enumerate().any(|(i, (pk, _weight))| {
570 pk_map
571 .iter()
572 .skip(i + 1)
573 .any(|(other_pk, _weight)| *pk == *other_pk)
574 })
575 {
576 return Err(FastCryptoError::InvalidInput);
577 }
578 Ok(self.to_owned())
579 }
580}