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