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 let pk =
151 Ed25519PublicKey::from_bytes(subsig_pubkey.as_ref()).map_err(|_| {
152 IotaError::InvalidSignature {
153 error: "Invalid ed25519 pk bytes".to_string(),
154 }
155 })?;
156 pk.verify(
157 &digest,
158 &s.try_into().map_err(|_| IotaError::InvalidSignature {
159 error: "Invalid ed25519 signature bytes".to_string(),
160 })?,
161 )
162 }
163 CompressedSignature::Secp256k1(s) => {
164 let pk =
165 Secp256k1PublicKey::from_bytes(subsig_pubkey.as_ref()).map_err(|_| {
166 IotaError::InvalidSignature {
167 error: "Invalid k1 pk bytes".to_string(),
168 }
169 })?;
170 pk.verify(
171 &digest,
172 &s.try_into().map_err(|_| IotaError::InvalidSignature {
173 error: "Invalid k1 signature bytes".to_string(),
174 })?,
175 )
176 }
177 CompressedSignature::Secp256r1(s) => {
178 let pk =
179 Secp256r1PublicKey::from_bytes(subsig_pubkey.as_ref()).map_err(|_| {
180 IotaError::InvalidSignature {
181 error: "Invalid r1 pk bytes".to_string(),
182 }
183 })?;
184 pk.verify(
185 &digest,
186 &s.try_into().map_err(|_| IotaError::InvalidSignature {
187 error: "Invalid r1 signature bytes".to_string(),
188 })?,
189 )
190 }
191 CompressedSignature::ZkLogin(z) => {
192 let authenticator = ZkLoginAuthenticator::from_bytes(&z.0).map_err(|_| {
193 IotaError::InvalidSignature {
194 error: "Invalid zklogin authenticator bytes".to_string(),
195 }
196 })?;
197 authenticator
198 .verify_claims(
199 value,
200 IotaAddress::from(subsig_pubkey),
201 verify_params,
202 zklogin_inputs_cache.clone(),
203 )
204 .map_err(|e| FastCryptoError::GeneralError(e.to_string()))
205 }
206 CompressedSignature::Passkey(bytes) => {
207 let authenticator =
208 PasskeyAuthenticator::from_bytes(&bytes.0).map_err(|_| {
209 IotaError::InvalidSignature {
210 error: "Invalid passkey authenticator bytes".to_string(),
211 }
212 })?;
213 authenticator
214 .verify_claims(
215 value,
216 IotaAddress::from(subsig_pubkey),
217 verify_params,
218 zklogin_inputs_cache.clone(),
219 )
220 .map_err(|e| FastCryptoError::GeneralError(e.to_string()))
221 }
222 };
223 if res.is_ok() {
224 weight_sum += *weight as u16;
225 } else {
226 return res.map_err(|e| IotaError::InvalidSignature {
227 error: format!(
228 "Invalid sig for pk={} address={:?} error={:?}",
229 subsig_pubkey.encode_base64(),
230 IotaAddress::from(subsig_pubkey),
231 e.to_string()
232 ),
233 });
234 }
235 }
236 if weight_sum >= self.multisig_pk.threshold {
237 Ok(())
238 } else {
239 Err(IotaError::InvalidSignature {
240 error: format!(
241 "Insufficient weight={:?} threshold={:?}",
242 weight_sum, self.multisig_pk.threshold
243 ),
244 })
245 }
246 }
247}
248
249pub fn as_indices(bitmap: u16) -> Result<Vec<u8>, IotaError> {
252 if bitmap > MAX_BITMAP_VALUE {
253 return Err(IotaError::InvalidSignature {
254 error: "Invalid bitmap".to_string(),
255 });
256 }
257 let mut res = Vec::new();
258 for i in 0..10 {
259 if bitmap & (1 << i) != 0 {
260 res.push(i as u8);
261 }
262 }
263 Ok(res)
264}
265
266impl MultiSig {
267 pub fn insecure_new(
269 sigs: Vec<CompressedSignature>,
270 bitmap: BitmapUnit,
271 multisig_pk: MultiSigPublicKey,
272 ) -> Self {
273 Self {
274 sigs,
275 bitmap,
276 multisig_pk,
277 bytes: OnceCell::new(),
278 }
279 }
280 pub fn combine(
286 full_sigs: Vec<GenericSignature>,
287 multisig_pk: MultiSigPublicKey,
288 ) -> Result<Self, IotaError> {
289 multisig_pk
290 .validate()
291 .map_err(|_| IotaError::InvalidSignature {
292 error: "Invalid multisig public key".to_string(),
293 })?;
294
295 if full_sigs.len() > multisig_pk.pk_map.len() || full_sigs.is_empty() {
296 return Err(IotaError::InvalidSignature {
297 error: "Invalid number of signatures".to_string(),
298 });
299 }
300 let mut bitmap = 0;
301 let mut sigs = Vec::with_capacity(full_sigs.len());
302 for s in full_sigs {
303 let pk = s.to_public_key()?;
304 let index = multisig_pk
305 .get_index(&pk)
306 .ok_or(IotaError::IncorrectSigner {
307 error: format!("pk does not exist: {:?}", pk),
308 })?;
309 if bitmap & (1 << index) != 0 {
310 return Err(IotaError::InvalidSignature {
311 error: "Duplicate public key".to_string(),
312 });
313 }
314 bitmap |= 1 << index;
315 sigs.push(s.to_compressed()?);
316 }
317
318 Ok(MultiSig {
319 sigs,
320 bitmap,
321 multisig_pk,
322 bytes: OnceCell::new(),
323 })
324 }
325
326 pub fn init_and_validate(&mut self) -> Result<Self, FastCryptoError> {
327 if self.sigs.len() > self.multisig_pk.pk_map.len()
328 || self.sigs.is_empty()
329 || self.bitmap > MAX_BITMAP_VALUE
330 {
331 return Err(FastCryptoError::InvalidInput);
332 }
333 self.multisig_pk.validate()?;
334 Ok(self.to_owned())
335 }
336
337 pub fn get_pk(&self) -> &MultiSigPublicKey {
338 &self.multisig_pk
339 }
340
341 pub fn get_sigs(&self) -> &[CompressedSignature] {
342 &self.sigs
343 }
344
345 pub fn get_zklogin_sigs(&self) -> Result<Vec<ZkLoginAuthenticator>, IotaError> {
346 let authenticator_as_bytes: Vec<_> = self
347 .sigs
348 .iter()
349 .filter_map(|s| match s {
350 CompressedSignature::ZkLogin(z) => Some(z),
351 _ => None,
352 })
353 .collect();
354 authenticator_as_bytes
355 .iter()
356 .map(|z| {
357 ZkLoginAuthenticator::from_bytes(&z.0).map_err(|_| IotaError::InvalidSignature {
358 error: "Invalid zklogin authenticator bytes".to_string(),
359 })
360 })
361 .collect()
362 }
363
364 pub fn get_indices(&self) -> Result<Vec<u8>, IotaError> {
365 as_indices(self.bitmap)
366 }
367
368 pub fn has_passkey_sigs(&self) -> bool {
369 self.sigs
370 .iter()
371 .any(|s| matches!(s, CompressedSignature::Passkey(_)))
372 }
373}
374
375impl ToFromBytes for MultiSig {
376 fn from_bytes(bytes: &[u8]) -> Result<MultiSig, FastCryptoError> {
377 if bytes.first().ok_or(FastCryptoError::InvalidInput)? != &SignatureScheme::MultiSig.flag()
379 {
380 return Err(FastCryptoError::InvalidInput);
381 }
382 let mut multisig: MultiSig =
383 bcs::from_bytes(&bytes[1..]).map_err(|_| FastCryptoError::InvalidSignature)?;
384 multisig.init_and_validate()
385 }
386}
387
388impl FromStr for MultiSig {
389 type Err = IotaError;
390
391 fn from_str(s: &str) -> Result<Self, Self::Err> {
392 let bytes = Base64::decode(s).map_err(|_| IotaError::InvalidSignature {
393 error: "Invalid base64 string".to_string(),
394 })?;
395 let sig = MultiSig::from_bytes(&bytes).map_err(|_| IotaError::InvalidSignature {
396 error: "Invalid multisig bytes".to_string(),
397 })?;
398 Ok(sig)
399 }
400}
401
402impl AsRef<[u8]> for MultiSig {
406 fn as_ref(&self) -> &[u8] {
407 self.bytes
408 .get_or_try_init::<_, eyre::Report>(|| {
409 let as_bytes = bcs::to_bytes(self).expect("BCS serialization should not fail");
410 let mut bytes = Vec::with_capacity(1 + as_bytes.len());
411 bytes.push(SignatureScheme::MultiSig.flag());
412 bytes.extend_from_slice(as_bytes.as_slice());
413 Ok(bytes)
414 })
415 .expect("OnceCell invariant violated")
416 }
417}
418
419#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
421pub struct MultiSigPublicKey {
422 pk_map: Vec<(PublicKey, WeightUnit)>,
424 threshold: ThresholdUnit,
427}
428
429impl MultiSigPublicKey {
430 pub fn insecure_new(pk_map: Vec<(PublicKey, WeightUnit)>, threshold: ThresholdUnit) -> Self {
432 Self { pk_map, threshold }
433 }
434
435 pub fn new(
436 pks: Vec<PublicKey>,
437 weights: Vec<WeightUnit>,
438 threshold: ThresholdUnit,
439 ) -> Result<Self, IotaError> {
440 if pks.is_empty()
441 || weights.is_empty()
442 || threshold == 0
443 || pks.len() != weights.len()
444 || pks.len() > MAX_SIGNER_IN_MULTISIG
445 || weights.contains(&0)
446 || weights
447 .iter()
448 .map(|w| *w as ThresholdUnit)
449 .sum::<ThresholdUnit>()
450 < threshold
451 || pks
452 .iter()
453 .enumerate()
454 .any(|(i, pk)| pks.iter().skip(i + 1).any(|other_pk| *pk == *other_pk))
455 {
456 return Err(IotaError::InvalidSignature {
457 error: "Invalid multisig public key construction".to_string(),
458 });
459 }
460
461 Ok(MultiSigPublicKey {
462 pk_map: pks.into_iter().zip(weights).collect(),
463 threshold,
464 })
465 }
466
467 pub fn get_index(&self, pk: &PublicKey) -> Option<u8> {
468 self.pk_map.iter().position(|x| &x.0 == pk).map(|x| x as u8)
469 }
470
471 pub fn threshold(&self) -> &ThresholdUnit {
472 &self.threshold
473 }
474
475 pub fn pubkeys(&self) -> &Vec<(PublicKey, WeightUnit)> {
476 &self.pk_map
477 }
478
479 pub fn validate(&self) -> Result<MultiSigPublicKey, FastCryptoError> {
480 let pk_map = self.pubkeys();
481 if self.threshold == 0
482 || pk_map.is_empty()
483 || pk_map.len() > MAX_SIGNER_IN_MULTISIG
484 || pk_map.iter().any(|(_pk, weight)| *weight == 0)
485 || pk_map
486 .iter()
487 .map(|(_pk, weight)| *weight as ThresholdUnit)
488 .sum::<ThresholdUnit>()
489 < self.threshold
490 || pk_map.iter().enumerate().any(|(i, (pk, _weight))| {
491 pk_map
492 .iter()
493 .skip(i + 1)
494 .any(|(other_pk, _weight)| *pk == *other_pk)
495 })
496 {
497 return Err(FastCryptoError::InvalidInput);
498 }
499 Ok(self.to_owned())
500 }
501}