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.get_zklogin_sigs()?.is_empty() && !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 message = bcs::to_bytes(&value).expect("Message serialization should not fail");
149 let mut hasher = DefaultHash::default();
150 hasher.update(message);
151 let digest = hasher.finalize().digest;
152 for (sig, i) in self.sigs.iter().zip_debug_eq(as_indices(self.bitmap)?) {
155 let (subsig_pubkey, weight) =
156 self.multisig_pk
157 .pk_map
158 .get(i as usize)
159 .ok_or(SuiErrorKind::InvalidSignature {
160 error: "Invalid public keys index".to_string(),
161 })?;
162 let res = match sig {
163 CompressedSignature::Ed25519(s) => {
164 if verify_params.additional_multisig_checks
165 && !matches!(subsig_pubkey.scheme(), SignatureScheme::ED25519)
166 {
167 return Err(SuiErrorKind::InvalidSignature {
168 error: format!(
169 "Invalid sig for pk={} address={:?} error=signature/pubkey type mismatch",
170 subsig_pubkey.encode_base64(),
171 SuiAddress::from(subsig_pubkey)
172 ),
173 }.into());
174 }
175 let pk =
176 Ed25519PublicKey::from_bytes(subsig_pubkey.as_ref()).map_err(|_| {
177 SuiErrorKind::InvalidSignature {
178 error: "Invalid ed25519 pk bytes".to_string(),
179 }
180 })?;
181 pk.verify(
182 &digest,
183 &s.try_into().map_err(|_| SuiErrorKind::InvalidSignature {
184 error: "Invalid ed25519 signature bytes".to_string(),
185 })?,
186 )
187 }
188 CompressedSignature::Secp256k1(s) => {
189 if verify_params.additional_multisig_checks
190 && !matches!(subsig_pubkey.scheme(), SignatureScheme::Secp256k1)
191 {
192 return Err(SuiErrorKind::InvalidSignature {
193 error: format!(
194 "Invalid sig for pk={} address={:?} error=signature/pubkey type mismatch",
195 subsig_pubkey.encode_base64(),
196 SuiAddress::from(subsig_pubkey)
197 ),
198 }.into());
199 }
200 let pk =
201 Secp256k1PublicKey::from_bytes(subsig_pubkey.as_ref()).map_err(|_| {
202 SuiErrorKind::InvalidSignature {
203 error: "Invalid k1 pk bytes".to_string(),
204 }
205 })?;
206 pk.verify(
207 &digest,
208 &s.try_into().map_err(|_| SuiErrorKind::InvalidSignature {
209 error: "Invalid k1 signature bytes".to_string(),
210 })?,
211 )
212 }
213 CompressedSignature::Secp256r1(s) => {
214 if verify_params.additional_multisig_checks
215 && !matches!(subsig_pubkey.scheme(), SignatureScheme::Secp256r1)
216 {
217 return Err(SuiErrorKind::InvalidSignature {
218 error: format!(
219 "Invalid sig for pk={} address={:?} error=signature/pubkey type mismatch",
220 subsig_pubkey.encode_base64(),
221 SuiAddress::from(subsig_pubkey)
222 ),
223 }.into());
224 }
225 let pk =
226 Secp256r1PublicKey::from_bytes(subsig_pubkey.as_ref()).map_err(|_| {
227 SuiErrorKind::InvalidSignature {
228 error: "Invalid r1 pk bytes".to_string(),
229 }
230 })?;
231 pk.verify(
232 &digest,
233 &s.try_into().map_err(|_| SuiErrorKind::InvalidSignature {
234 error: "Invalid r1 signature bytes".to_string(),
235 })?,
236 )
237 }
238 CompressedSignature::ZkLogin(z) => {
239 if verify_params.additional_multisig_checks
240 && !matches!(
241 subsig_pubkey.scheme(),
242 SignatureScheme::ZkLoginAuthenticator
243 )
244 {
245 return Err(SuiErrorKind::InvalidSignature {
246 error: format!(
247 "Invalid sig for pk={} address={:?} error=signature/pubkey type mismatch",
248 subsig_pubkey.encode_base64(),
249 SuiAddress::from(subsig_pubkey)
250 ),
251 }.into());
252 }
253 let authenticator = ZkLoginAuthenticator::from_bytes(&z.0).map_err(|_| {
254 SuiErrorKind::InvalidSignature {
255 error: "Invalid zklogin authenticator bytes".to_string(),
256 }
257 })?;
258 authenticator
259 .verify_claims(
260 value,
261 SuiAddress::from(subsig_pubkey),
262 verify_params,
263 zklogin_inputs_cache.clone(),
264 )
265 .map_err(|e| FastCryptoError::GeneralError(e.to_string()))
266 }
267 CompressedSignature::Passkey(bytes) => {
268 let authenticator =
269 PasskeyAuthenticator::from_bytes(&bytes.0).map_err(|_| {
270 SuiErrorKind::InvalidSignature {
271 error: "Invalid passkey authenticator bytes".to_string(),
272 }
273 })?;
274 authenticator
275 .verify_claims(
276 value,
277 SuiAddress::from(subsig_pubkey),
278 verify_params,
279 zklogin_inputs_cache.clone(),
280 )
281 .map_err(|e| FastCryptoError::GeneralError(e.to_string()))
282 }
283 };
284 if res.is_ok() {
285 weight_sum += *weight as u16;
286 } else {
287 return res.map_err(|e| {
288 SuiErrorKind::InvalidSignature {
289 error: format!(
290 "Invalid sig for pk={} address={:?} error={:?}",
291 subsig_pubkey.encode_base64(),
292 SuiAddress::from(subsig_pubkey),
293 e.to_string()
294 ),
295 }
296 .into()
297 });
298 }
299 }
300 if weight_sum >= self.multisig_pk.threshold {
301 Ok(())
302 } else {
303 Err(SuiErrorKind::InvalidSignature {
304 error: format!(
305 "Insufficient weight={:?} threshold={:?}",
306 weight_sum, self.multisig_pk.threshold
307 ),
308 }
309 .into())
310 }
311 }
312}
313
314pub fn as_indices(bitmap: u16) -> Result<Vec<u8>, SuiError> {
317 if bitmap > MAX_BITMAP_VALUE {
318 return Err(SuiErrorKind::InvalidSignature {
319 error: "Invalid bitmap".to_string(),
320 }
321 .into());
322 }
323 let mut res = Vec::new();
324 for i in 0..10 {
325 if bitmap & (1 << i) != 0 {
326 res.push(i as u8);
327 }
328 }
329 Ok(res)
330}
331
332impl MultiSig {
333 pub fn insecure_new(
335 sigs: Vec<CompressedSignature>,
336 bitmap: BitmapUnit,
337 multisig_pk: MultiSigPublicKey,
338 ) -> Self {
339 Self {
340 sigs,
341 bitmap,
342 multisig_pk,
343 bytes: OnceCell::new(),
344 }
345 }
346 pub fn combine(
351 full_sigs: Vec<GenericSignature>,
352 multisig_pk: MultiSigPublicKey,
353 ) -> Result<Self, SuiError> {
354 multisig_pk
355 .validate()
356 .map_err(|_| SuiErrorKind::InvalidSignature {
357 error: "Invalid multisig public key".to_string(),
358 })?;
359
360 if full_sigs.len() > multisig_pk.pk_map.len() || full_sigs.is_empty() {
361 return Err(SuiErrorKind::InvalidSignature {
362 error: "Invalid number of signatures".to_string(),
363 }
364 .into());
365 }
366 let mut bitmap = 0;
367 let mut sigs = Vec::with_capacity(full_sigs.len());
368 for s in full_sigs {
369 let pk = s.to_public_key()?;
370 let index = multisig_pk
371 .get_index(&pk)
372 .ok_or(SuiErrorKind::IncorrectSigner {
373 error: format!("pk does not exist: {:?}", pk),
374 })?;
375 if bitmap & (1 << index) != 0 {
376 return Err(SuiErrorKind::InvalidSignature {
377 error: "Duplicate public key".to_string(),
378 }
379 .into());
380 }
381 bitmap |= 1 << index;
382 sigs.push(s.to_compressed()?);
383 }
384
385 Ok(MultiSig {
386 sigs,
387 bitmap,
388 multisig_pk,
389 bytes: OnceCell::new(),
390 })
391 }
392
393 pub fn init_and_validate(&mut self) -> Result<Self, FastCryptoError> {
394 if self.sigs.len() > self.multisig_pk.pk_map.len()
395 || self.sigs.is_empty()
396 || self.bitmap > MAX_BITMAP_VALUE
397 {
398 return Err(FastCryptoError::InvalidInput);
399 }
400 self.multisig_pk.validate()?;
401 Ok(self.to_owned())
402 }
403
404 pub fn get_pk(&self) -> &MultiSigPublicKey {
405 &self.multisig_pk
406 }
407
408 pub fn get_sigs(&self) -> &[CompressedSignature] {
409 &self.sigs
410 }
411
412 pub fn get_bitmap(&self) -> u16 {
413 self.bitmap
414 }
415
416 pub fn get_zklogin_sigs(&self) -> Result<Vec<ZkLoginAuthenticator>, SuiError> {
417 let authenticator_as_bytes: Vec<_> = self
418 .sigs
419 .iter()
420 .filter_map(|s| match s {
421 CompressedSignature::ZkLogin(z) => Some(z),
422 _ => None,
423 })
424 .collect();
425 authenticator_as_bytes
426 .iter()
427 .map(|z| {
428 ZkLoginAuthenticator::from_bytes(&z.0).map_err(|_| {
429 SuiErrorKind::InvalidSignature {
430 error: "Invalid zklogin authenticator bytes".to_string(),
431 }
432 .into()
433 })
434 })
435 .collect()
436 }
437
438 pub fn get_indices(&self) -> Result<Vec<u8>, SuiError> {
439 as_indices(self.bitmap)
440 }
441
442 pub fn has_passkey_sigs(&self) -> bool {
443 self.sigs
444 .iter()
445 .any(|s| matches!(s, CompressedSignature::Passkey(_)))
446 }
447}
448
449impl ToFromBytes for MultiSig {
450 fn from_bytes(bytes: &[u8]) -> Result<MultiSig, FastCryptoError> {
451 if bytes.first().ok_or(FastCryptoError::InvalidInput)? != &SignatureScheme::MultiSig.flag()
453 {
454 return Err(FastCryptoError::InvalidInput);
455 }
456 let mut multisig: MultiSig =
457 bcs::from_bytes(&bytes[1..]).map_err(|_| FastCryptoError::InvalidSignature)?;
458 multisig.init_and_validate()
459 }
460}
461
462impl FromStr for MultiSig {
463 type Err = SuiError;
464
465 fn from_str(s: &str) -> Result<Self, Self::Err> {
466 let bytes = Base64::decode(s).map_err(|_| SuiErrorKind::InvalidSignature {
467 error: "Invalid base64 string".to_string(),
468 })?;
469 let sig = MultiSig::from_bytes(&bytes).map_err(|_| SuiErrorKind::InvalidSignature {
470 error: "Invalid multisig bytes".to_string(),
471 })?;
472 Ok(sig)
473 }
474}
475
476impl AsRef<[u8]> for MultiSig {
480 fn as_ref(&self) -> &[u8] {
481 self.bytes
482 .get_or_try_init::<_, eyre::Report>(|| {
483 let as_bytes = bcs::to_bytes(self).expect("BCS serialization should not fail");
484 let mut bytes = Vec::with_capacity(1 + as_bytes.len());
485 bytes.push(SignatureScheme::MultiSig.flag());
486 bytes.extend_from_slice(as_bytes.as_slice());
487 Ok(bytes)
488 })
489 .expect("OnceCell invariant violated")
490 }
491}
492
493#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
495pub struct MultiSigPublicKey {
496 pk_map: Vec<(PublicKey, WeightUnit)>,
498 threshold: ThresholdUnit,
500}
501
502impl MultiSigPublicKey {
503 pub fn insecure_new(pk_map: Vec<(PublicKey, WeightUnit)>, threshold: ThresholdUnit) -> Self {
505 Self { pk_map, threshold }
506 }
507
508 pub fn new(
509 pks: Vec<PublicKey>,
510 weights: Vec<WeightUnit>,
511 threshold: ThresholdUnit,
512 ) -> Result<Self, SuiError> {
513 if pks.is_empty()
514 || weights.is_empty()
515 || threshold == 0
516 || pks.len() != weights.len()
517 || pks.len() > MAX_SIGNER_IN_MULTISIG
518 || weights.contains(&0)
519 || weights
520 .iter()
521 .map(|w| *w as ThresholdUnit)
522 .sum::<ThresholdUnit>()
523 < threshold
524 || pks
525 .iter()
526 .enumerate()
527 .any(|(i, pk)| pks.iter().skip(i + 1).any(|other_pk| *pk == *other_pk))
528 {
529 return Err(SuiErrorKind::InvalidSignature {
530 error: "Invalid multisig public key construction".to_string(),
531 }
532 .into());
533 }
534
535 Ok(MultiSigPublicKey {
536 pk_map: pks.into_iter().zip_debug_eq(weights).collect(),
537 threshold,
538 })
539 }
540
541 pub fn get_index(&self, pk: &PublicKey) -> Option<u8> {
542 self.pk_map.iter().position(|x| &x.0 == pk).map(|x| x as u8)
543 }
544
545 pub fn threshold(&self) -> &ThresholdUnit {
546 &self.threshold
547 }
548
549 pub fn pubkeys(&self) -> &Vec<(PublicKey, WeightUnit)> {
550 &self.pk_map
551 }
552
553 pub fn validate(&self) -> Result<MultiSigPublicKey, FastCryptoError> {
554 let pk_map = self.pubkeys();
555 if self.threshold == 0
556 || pk_map.is_empty()
557 || pk_map.len() > MAX_SIGNER_IN_MULTISIG
558 || pk_map.iter().any(|(_pk, weight)| *weight == 0)
559 || pk_map
560 .iter()
561 .map(|(_pk, weight)| *weight as ThresholdUnit)
562 .sum::<ThresholdUnit>()
563 < self.threshold
564 || pk_map.iter().enumerate().any(|(i, (pk, _weight))| {
565 pk_map
566 .iter()
567 .skip(i + 1)
568 .any(|(other_pk, _weight)| *pk == *other_pk)
569 })
570 {
571 return Err(FastCryptoError::InvalidInput);
572 }
573 Ok(self.to_owned())
574 }
575}