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 once_cell::sync::OnceCell;
24use schemars::JsonSchema;
25use serde::{Deserialize, Serialize};
26use serde_with::serde_as;
27use shared_crypto::intent::IntentMessage;
28use std::{
29 hash::{Hash, Hasher},
30 str::FromStr,
31 sync::Arc,
32};
33
34use crate::{
35 base_types::{EpochId, SuiAddress},
36 crypto::PublicKey,
37 error::SuiError,
38};
39
40#[cfg(test)]
41#[path = "unit_tests/multisig_tests.rs"]
42mod multisig_tests;
43
44pub type WeightUnit = u8;
45pub type ThresholdUnit = u16;
46pub type BitmapUnit = u16;
47pub const MAX_SIGNER_IN_MULTISIG: usize = 10;
48pub const MAX_BITMAP_VALUE: BitmapUnit = 0b1111111111;
49#[serde_as]
51#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)]
52pub struct MultiSig {
53 sigs: Vec<CompressedSignature>,
55 bitmap: BitmapUnit,
57 multisig_pk: MultiSigPublicKey,
59 #[serde(skip)]
61 bytes: OnceCell<Vec<u8>>,
62}
63
64impl PartialEq for MultiSig {
66 fn eq(&self, other: &Self) -> bool {
67 self.sigs == other.sigs
68 && self.bitmap == other.bitmap
69 && self.multisig_pk == other.multisig_pk
70 }
71}
72
73impl Eq for MultiSig {}
75
76impl Hash for MultiSig {
78 fn hash<H: Hasher>(&self, state: &mut H) {
79 self.as_ref().hash(state);
80 }
81}
82
83impl AuthenticatorTrait for MultiSig {
84 fn verify_user_authenticator_epoch(
85 &self,
86 epoch_id: EpochId,
87 max_epoch_upper_bound_delta: Option<u64>,
88 ) -> Result<(), SuiError> {
89 self.get_zklogin_sigs()?.iter().try_for_each(|s| {
92 s.verify_user_authenticator_epoch(epoch_id, max_epoch_upper_bound_delta)
93 })
94 }
95
96 fn verify_claims<T>(
97 &self,
98 value: &IntentMessage<T>,
99 multisig_address: SuiAddress,
100 verify_params: &VerifyParams,
101 zklogin_inputs_cache: Arc<VerifiedDigestCache<ZKLoginInputsDigest>>,
102 ) -> Result<(), SuiError>
103 where
104 T: Serialize,
105 {
106 self.multisig_pk
107 .validate()
108 .map_err(|_| SuiErrorKind::InvalidSignature {
109 error: "Invalid multisig pubkey".to_string(),
110 })?;
111
112 if verify_params.validate_zklogin_public_identifier {
114 for (pk, _weight) in &self.multisig_pk.pk_map {
115 if let PublicKey::ZkLogin(zklogin_pk) = pk {
116 zklogin_pk
117 .validate()
118 .map_err(|e| SuiErrorKind::InvalidSignature {
119 error: format!("Invalid zkLogin pk in multisig: {}", e),
120 })?;
121 }
122 }
123 }
124
125 if SuiAddress::from(&self.multisig_pk) != multisig_address {
126 return Err(SuiErrorKind::InvalidSignature {
127 error: "Invalid address derived from pks".to_string(),
128 }
129 .into());
130 }
131
132 if !self.get_zklogin_sigs()?.is_empty() && !verify_params.accept_zklogin_in_multisig {
133 return Err(SuiErrorKind::InvalidSignature {
134 error: "zkLogin sig not supported inside multisig".to_string(),
135 }
136 .into());
137 }
138
139 if self.has_passkey_sigs() && !verify_params.accept_passkey_in_multisig {
140 return Err(SuiErrorKind::InvalidSignature {
141 error: "Passkey sig not supported inside multisig".to_string(),
142 }
143 .into());
144 }
145
146 let mut weight_sum: u16 = 0;
147 let message = bcs::to_bytes(&value).expect("Message serialization should not fail");
148 let mut hasher = DefaultHash::default();
149 hasher.update(message);
150 let digest = hasher.finalize().digest;
151 for (sig, i) in self.sigs.iter().zip(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
448impl ToFromBytes for MultiSig {
449 fn from_bytes(bytes: &[u8]) -> Result<MultiSig, FastCryptoError> {
450 if bytes.first().ok_or(FastCryptoError::InvalidInput)? != &SignatureScheme::MultiSig.flag()
452 {
453 return Err(FastCryptoError::InvalidInput);
454 }
455 let mut multisig: MultiSig =
456 bcs::from_bytes(&bytes[1..]).map_err(|_| FastCryptoError::InvalidSignature)?;
457 multisig.init_and_validate()
458 }
459}
460
461impl FromStr for MultiSig {
462 type Err = SuiError;
463
464 fn from_str(s: &str) -> Result<Self, Self::Err> {
465 let bytes = Base64::decode(s).map_err(|_| SuiErrorKind::InvalidSignature {
466 error: "Invalid base64 string".to_string(),
467 })?;
468 let sig = MultiSig::from_bytes(&bytes).map_err(|_| SuiErrorKind::InvalidSignature {
469 error: "Invalid multisig bytes".to_string(),
470 })?;
471 Ok(sig)
472 }
473}
474
475impl AsRef<[u8]> for MultiSig {
479 fn as_ref(&self) -> &[u8] {
480 self.bytes
481 .get_or_try_init::<_, eyre::Report>(|| {
482 let as_bytes = bcs::to_bytes(self).expect("BCS serialization should not fail");
483 let mut bytes = Vec::with_capacity(1 + as_bytes.len());
484 bytes.push(SignatureScheme::MultiSig.flag());
485 bytes.extend_from_slice(as_bytes.as_slice());
486 Ok(bytes)
487 })
488 .expect("OnceCell invariant violated")
489 }
490}
491
492#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
494pub struct MultiSigPublicKey {
495 pk_map: Vec<(PublicKey, WeightUnit)>,
497 threshold: ThresholdUnit,
499}
500
501impl MultiSigPublicKey {
502 pub fn insecure_new(pk_map: Vec<(PublicKey, WeightUnit)>, threshold: ThresholdUnit) -> Self {
504 Self { pk_map, threshold }
505 }
506
507 pub fn new(
508 pks: Vec<PublicKey>,
509 weights: Vec<WeightUnit>,
510 threshold: ThresholdUnit,
511 ) -> Result<Self, SuiError> {
512 if pks.is_empty()
513 || weights.is_empty()
514 || threshold == 0
515 || pks.len() != weights.len()
516 || pks.len() > MAX_SIGNER_IN_MULTISIG
517 || weights.contains(&0)
518 || weights
519 .iter()
520 .map(|w| *w as ThresholdUnit)
521 .sum::<ThresholdUnit>()
522 < threshold
523 || pks
524 .iter()
525 .enumerate()
526 .any(|(i, pk)| pks.iter().skip(i + 1).any(|other_pk| *pk == *other_pk))
527 {
528 return Err(SuiErrorKind::InvalidSignature {
529 error: "Invalid multisig public key construction".to_string(),
530 }
531 .into());
532 }
533
534 Ok(MultiSigPublicKey {
535 pk_map: pks.into_iter().zip(weights).collect(),
536 threshold,
537 })
538 }
539
540 pub fn get_index(&self, pk: &PublicKey) -> Option<u8> {
541 self.pk_map.iter().position(|x| &x.0 == pk).map(|x| x as u8)
542 }
543
544 pub fn threshold(&self) -> &ThresholdUnit {
545 &self.threshold
546 }
547
548 pub fn pubkeys(&self) -> &Vec<(PublicKey, WeightUnit)> {
549 &self.pk_map
550 }
551
552 pub fn validate(&self) -> Result<MultiSigPublicKey, FastCryptoError> {
553 let pk_map = self.pubkeys();
554 if self.threshold == 0
555 || pk_map.is_empty()
556 || pk_map.len() > MAX_SIGNER_IN_MULTISIG
557 || pk_map.iter().any(|(_pk, weight)| *weight == 0)
558 || pk_map
559 .iter()
560 .map(|(_pk, weight)| *weight as ThresholdUnit)
561 .sum::<ThresholdUnit>()
562 < self.threshold
563 || pk_map.iter().enumerate().any(|(i, (pk, _weight))| {
564 pk_map
565 .iter()
566 .skip(i + 1)
567 .any(|(other_pk, _weight)| *pk == *other_pk)
568 })
569 {
570 return Err(FastCryptoError::InvalidInput);
571 }
572 Ok(self.to_owned())
573 }
574}