1use super::Ed25519PublicKey;
2use super::Ed25519Signature;
3use super::MlDsa65PublicKey;
4use super::MlDsa65Signature;
5use super::PasskeyAuthenticator;
6use super::PasskeyPublicKey;
7use super::Secp256k1PublicKey;
8use super::Secp256k1Signature;
9use super::Secp256r1PublicKey;
10use super::Secp256r1Signature;
11use super::SignatureScheme;
12use super::zklogin::ZkLoginAuthenticator;
13use super::zklogin::ZkLoginPublicIdentifier;
14
15pub type WeightUnit = u8;
16pub type ThresholdUnit = u16;
17pub type BitmapUnit = u16;
18
19const MAX_COMMITTEE_SIZE: usize = 10;
20#[derive(Clone, Debug, PartialEq, Eq)]
52#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
53#[non_exhaustive]
54pub enum MultisigMemberPublicKey {
55 Ed25519(Ed25519PublicKey),
56 Secp256k1(Secp256k1PublicKey),
57 Secp256r1(Secp256r1PublicKey),
58 ZkLogin(ZkLoginPublicIdentifier),
59 Passkey(PasskeyPublicKey),
60 MlDsa65(Box<MlDsa65PublicKey>),
61}
62
63#[derive(Clone, Debug, PartialEq, Eq)]
81#[cfg_attr(
82 feature = "serde",
83 derive(serde_derive::Serialize, serde_derive::Deserialize)
84)]
85#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
86pub struct MultisigMember {
87 public_key: MultisigMemberPublicKey,
88 weight: WeightUnit,
89}
90
91impl MultisigMember {
92 pub fn new(public_key: MultisigMemberPublicKey, weight: WeightUnit) -> Self {
94 Self { public_key, weight }
95 }
96
97 pub fn public_key(&self) -> &MultisigMemberPublicKey {
99 &self.public_key
100 }
101
102 pub fn weight(&self) -> WeightUnit {
104 self.weight
105 }
106}
107
108#[derive(Debug, Clone, PartialEq, Eq)]
146#[cfg_attr(
147 feature = "serde",
148 derive(serde_derive::Serialize, serde_derive::Deserialize)
149)]
150#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
151pub struct MultisigCommittee {
152 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=10).lift()))]
154 members: Vec<MultisigMember>,
155
156 threshold: ThresholdUnit,
159}
160
161impl MultisigCommittee {
162 pub fn new(members: Vec<MultisigMember>, threshold: ThresholdUnit) -> Self {
167 Self { members, threshold }
168 }
169
170 pub fn members(&self) -> &[MultisigMember] {
172 &self.members
173 }
174
175 pub fn threshold(&self) -> ThresholdUnit {
178 self.threshold
179 }
180
181 pub fn scheme(&self) -> SignatureScheme {
183 SignatureScheme::Multisig
184 }
185
186 pub fn is_valid(&self) -> bool {
196 self.threshold != 0
197 && !self.members.is_empty()
198 && self.members.len() <= MAX_COMMITTEE_SIZE
199 && !self.members.iter().any(|member| member.weight == 0)
200 && self
201 .members
202 .iter()
203 .map(|member| member.weight as ThresholdUnit)
204 .sum::<ThresholdUnit>()
205 >= self.threshold
206 && !self.members.iter().enumerate().any(|(i, member)| {
207 self.members
208 .iter()
209 .skip(i + 1)
210 .any(|m| member.public_key == m.public_key)
211 })
212 }
213}
214
215#[derive(Debug, Clone)]
241#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
242pub struct MultisigAggregatedSignature {
243 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=10).lift()))]
247 signatures: Vec<MultisigMemberSignature>,
248 bitmap: BitmapUnit,
251 #[cfg_attr(feature = "proptest", strategy(proptest::strategy::Just(None)))]
254 legacy_bitmap: Option<crate::Bitmap>,
255 committee: MultisigCommittee,
258}
259
260impl MultisigAggregatedSignature {
261 pub fn new(
268 committee: MultisigCommittee,
269 signatures: Vec<MultisigMemberSignature>,
270 bitmap: BitmapUnit,
271 ) -> Self {
272 Self {
273 signatures,
274 bitmap,
275 legacy_bitmap: None,
276 committee,
277 }
278 }
279
280 pub fn signatures(&self) -> &[MultisigMemberSignature] {
282 &self.signatures
283 }
284
285 pub fn bitmap(&self) -> BitmapUnit {
287 self.bitmap
288 }
289
290 pub fn legacy_bitmap(&self) -> Option<&crate::Bitmap> {
292 self.legacy_bitmap.as_ref()
293 }
294
295 #[deprecated(note = "use `try_with_legacy_bitmap`, which rejects unserializable bitmaps")]
297 pub fn with_legacy_bitmap(&mut self, legacy_bitmap: crate::Bitmap) {
298 self.legacy_bitmap = Some(legacy_bitmap);
299 }
300
301 pub fn try_with_legacy_bitmap(
311 &mut self,
312 legacy_bitmap: crate::Bitmap,
313 ) -> Result<(), InvalidLegacyBitmapError> {
314 validate_legacy_bitmap(&self.committee, self.bitmap, &legacy_bitmap)?;
315 self.legacy_bitmap = Some(legacy_bitmap);
316 Ok(())
317 }
318
319 pub fn committee(&self) -> &MultisigCommittee {
321 &self.committee
322 }
323}
324
325impl PartialEq for MultisigAggregatedSignature {
326 fn eq(&self, other: &Self) -> bool {
327 self.bitmap == other.bitmap
337 && self.legacy_bitmap == other.legacy_bitmap
338 && self.signatures == other.signatures
339 && self.committee == other.committee
340 }
341}
342
343impl Eq for MultisigAggregatedSignature {}
344
345fn roaring_bitmap_to_u16(roaring: &crate::Bitmap) -> Result<BitmapUnit, &'static str> {
347 let mut val = 0;
348 for i in roaring.iter() {
349 if i >= MAX_COMMITTEE_SIZE as u32 {
350 return Err("invalid bitmap");
351 }
352 val |= 1 << i as u8;
353 }
354 Ok(val)
355}
356
357fn validate_legacy_bitmap(
367 committee: &MultisigCommittee,
368 bitmap: BitmapUnit,
369 legacy_bitmap: &crate::Bitmap,
370) -> Result<(), InvalidLegacyBitmapError> {
371 let derived = roaring_bitmap_to_u16(legacy_bitmap).map_err(InvalidLegacyBitmapError)?;
372 if derived != bitmap {
373 return Err(InvalidLegacyBitmapError(
374 "bitmap does not match legacy_bitmap",
375 ));
376 }
377
378 for member in &committee.members {
379 let reason = match member.public_key {
380 MultisigMemberPublicKey::Ed25519(_)
381 | MultisigMemberPublicKey::Secp256k1(_)
382 | MultisigMemberPublicKey::Secp256r1(_) => continue,
383 MultisigMemberPublicKey::ZkLogin(_) => {
384 "zklogin member is not representable in legacy multisig"
385 }
386 MultisigMemberPublicKey::Passkey(_) => {
387 "passkey member is not representable in legacy multisig"
388 }
389 MultisigMemberPublicKey::MlDsa65(_) => {
390 "mldsa65 member is not representable in legacy multisig"
391 }
392 };
393 return Err(InvalidLegacyBitmapError(reason));
394 }
395
396 Ok(())
397}
398
399#[derive(Debug, Clone, PartialEq, Eq)]
402pub struct InvalidLegacyBitmapError(&'static str);
403
404impl std::fmt::Display for InvalidLegacyBitmapError {
405 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
406 f.write_str(self.0)
407 }
408}
409
410impl std::error::Error for InvalidLegacyBitmapError {}
411
412#[derive(Debug, Clone, PartialEq, Eq)]
430#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
431#[non_exhaustive]
432pub enum MultisigMemberSignature {
433 Ed25519(Ed25519Signature),
434 Secp256k1(Secp256k1Signature),
435 Secp256r1(Secp256r1Signature),
436 ZkLogin(Box<ZkLoginAuthenticator>),
437 Passkey(PasskeyAuthenticator),
438 MlDsa65(Box<MlDsa65Signature>),
439}
440
441#[cfg(feature = "serde")]
442#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
443mod serialization {
444 use super::*;
445 use crate::Ed25519PublicKey;
446 use crate::PasskeyPublicKey;
447 use crate::Secp256k1PublicKey;
448 use crate::Secp256r1PublicKey;
449 use crate::SignatureScheme;
450 use crate::crypto::Base64Array33;
451 use crate::crypto::Base64Array34;
452 use base64ct::Base64;
453 use base64ct::Encoding;
454 use serde::Deserialize;
455 use serde::Deserializer;
456 use serde::Serialize;
457 use serde::Serializer;
458 use serde_with::Bytes;
459 use serde_with::DeserializeAs;
460 use serde_with::SerializeAs;
461 use std::borrow::Cow;
462
463 pub struct Base64MultisigMemberPublicKey;
464
465 impl SerializeAs<MultisigMemberPublicKey> for Base64MultisigMemberPublicKey {
466 fn serialize_as<S>(
467 source: &MultisigMemberPublicKey,
468 serializer: S,
469 ) -> Result<S::Ok, S::Error>
470 where
471 S: Serializer,
472 {
473 match source {
474 MultisigMemberPublicKey::Ed25519(public_key) => {
475 let mut buf = [0; 1 + Ed25519PublicKey::LENGTH];
476 buf[0] = SignatureScheme::Ed25519 as u8;
477 buf[1..].copy_from_slice(public_key.as_ref());
478 Base64Array33::serialize_as(&buf, serializer)
479 }
480 MultisigMemberPublicKey::Secp256k1(public_key) => {
481 let mut buf = [0; 1 + Secp256k1PublicKey::LENGTH];
482 buf[0] = SignatureScheme::Secp256k1 as u8;
483 buf[1..].copy_from_slice(public_key.as_ref());
484 Base64Array34::serialize_as(&buf, serializer)
485 }
486 MultisigMemberPublicKey::Secp256r1(public_key) => {
487 let mut buf = [0; 1 + Secp256r1PublicKey::LENGTH];
488 buf[0] = SignatureScheme::Secp256r1 as u8;
489 buf[1..].copy_from_slice(public_key.as_ref());
490 Base64Array34::serialize_as(&buf, serializer)
491 }
492 MultisigMemberPublicKey::ZkLogin(_) => Err(serde::ser::Error::custom(
493 "zklogin not supported in legacy multisig",
494 )),
495 MultisigMemberPublicKey::Passkey(_) => Err(serde::ser::Error::custom(
496 "passkey not supported in legacy multisig",
497 )),
498 MultisigMemberPublicKey::MlDsa65(_) => Err(serde::ser::Error::custom(
499 "mldsa65 not supported in legacy multisig",
500 )),
501 }
502 }
503 }
504
505 impl<'de> DeserializeAs<'de, MultisigMemberPublicKey> for Base64MultisigMemberPublicKey {
506 fn deserialize_as<D>(deserializer: D) -> Result<MultisigMemberPublicKey, D::Error>
507 where
508 D: Deserializer<'de>,
509 {
510 let b64: Cow<'de, str> = Deserialize::deserialize(deserializer)?;
511 let bytes = Base64::decode_vec(&b64).map_err(serde::de::Error::custom)?;
512 let flag = SignatureScheme::from_byte(
513 *bytes
514 .first()
515 .ok_or_else(|| serde::de::Error::custom("missing signature scheme flag"))?,
516 )
517 .map_err(serde::de::Error::custom)?;
518 let public_key_bytes = &bytes[1..];
519 match flag {
520 SignatureScheme::Ed25519 => {
521 let public_key = Ed25519PublicKey::from_bytes(public_key_bytes)
522 .map_err(serde::de::Error::custom)?;
523 Ok(MultisigMemberPublicKey::Ed25519(public_key))
524 }
525 SignatureScheme::Secp256k1 => {
526 let public_key = Secp256k1PublicKey::from_bytes(public_key_bytes)
527 .map_err(serde::de::Error::custom)?;
528 Ok(MultisigMemberPublicKey::Secp256k1(public_key))
529 }
530 SignatureScheme::Secp256r1 => {
531 let public_key = Secp256r1PublicKey::from_bytes(public_key_bytes)
532 .map_err(serde::de::Error::custom)?;
533 Ok(MultisigMemberPublicKey::Secp256r1(public_key))
534 }
535 SignatureScheme::Multisig
536 | SignatureScheme::Bls12381
537 | SignatureScheme::ZkLogin
538 | SignatureScheme::Passkey
539 | SignatureScheme::MlDsa65 => {
540 Err(serde::de::Error::custom("invalid public key type"))
541 }
542 }
543 }
544 }
545
546 pub struct LegacyMultisigMember;
547
548 impl SerializeAs<MultisigMember> for LegacyMultisigMember {
549 fn serialize_as<S>(source: &MultisigMember, serializer: S) -> Result<S::Ok, S::Error>
550 where
551 S: Serializer,
552 {
553 #[derive(serde_derive::Serialize)]
554 struct LegacyMember<'a> {
555 #[serde(with = "::serde_with::As::<Base64MultisigMemberPublicKey>")]
556 public_key: &'a MultisigMemberPublicKey,
557 weight: WeightUnit,
558 }
559
560 let legacy = LegacyMember {
561 public_key: &source.public_key,
562 weight: source.weight,
563 };
564
565 legacy.serialize(serializer)
566 }
567 }
568
569 impl<'de> DeserializeAs<'de, MultisigMember> for LegacyMultisigMember {
570 fn deserialize_as<D>(deserializer: D) -> Result<MultisigMember, D::Error>
571 where
572 D: Deserializer<'de>,
573 {
574 #[derive(serde_derive::Deserialize)]
575 struct LegacyMember {
576 #[serde(with = "::serde_with::As::<Base64MultisigMemberPublicKey>")]
577 public_key: MultisigMemberPublicKey,
578 weight: WeightUnit,
579 }
580
581 let legacy = LegacyMember::deserialize(deserializer)?;
582
583 Ok(MultisigMember {
584 public_key: legacy.public_key,
585 weight: legacy.weight,
586 })
587 }
588 }
589
590 #[derive(serde_derive::Deserialize)]
591 pub struct Multisig {
592 signatures: Vec<MultisigMemberSignature>,
593 bitmap: BitmapUnit,
594 committee: MultisigCommittee,
595 }
596
597 #[derive(serde_derive::Serialize)]
598 pub struct MultisigRef<'a> {
599 signatures: &'a [MultisigMemberSignature],
600 bitmap: BitmapUnit,
601 committee: &'a MultisigCommittee,
602 }
603
604 #[derive(serde_derive::Deserialize)]
605 pub struct LegacyMultisig {
606 signatures: Vec<MultisigMemberSignature>,
607 bitmap: crate::Bitmap,
608 committee: LegacyMultisigCommittee,
609 }
610
611 #[derive(serde_derive::Serialize)]
612 pub struct LegacyMultisigRef<'a> {
613 signatures: &'a [MultisigMemberSignature],
614 bitmap: &'a crate::Bitmap,
615 committee: LegacyMultisigCommitteeRef<'a>,
616 }
617
618 #[derive(serde_derive::Deserialize)]
619 struct LegacyMultisigCommittee {
620 #[serde(with = "::serde_with::As::<Vec<LegacyMultisigMember>>")]
621 members: Vec<MultisigMember>,
622 threshold: ThresholdUnit,
623 }
624
625 #[derive(serde_derive::Serialize)]
626 struct LegacyMultisigCommitteeRef<'a> {
627 #[serde(with = "::serde_with::As::<&[LegacyMultisigMember]>")]
628 members: &'a [MultisigMember],
629 threshold: ThresholdUnit,
630 }
631
632 #[derive(serde_derive::Deserialize)]
633 struct ReadableMultisigAggregatedSignature {
634 signatures: Vec<MultisigMemberSignature>,
635 bitmap: BitmapUnit,
636 legacy_bitmap: Option<crate::Bitmap>,
637 committee: MultisigCommittee,
638 }
639
640 #[derive(serde_derive::Serialize)]
641 struct ReadableMultisigAggregatedSignatureRef<'a> {
642 signatures: &'a [MultisigMemberSignature],
643 bitmap: BitmapUnit,
644 #[serde(skip_serializing_if = "Option::is_none")]
645 legacy_bitmap: &'a Option<crate::Bitmap>,
646 committee: &'a MultisigCommittee,
647 }
648
649 impl Serialize for MultisigAggregatedSignature {
650 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
651 where
652 S: Serializer,
653 {
654 if serializer.is_human_readable() {
655 let readable = ReadableMultisigAggregatedSignatureRef {
656 signatures: &self.signatures,
657 bitmap: self.bitmap,
658 legacy_bitmap: &self.legacy_bitmap,
659 committee: &self.committee,
660 };
661 readable.serialize(serializer)
662 } else {
663 let bytes = self.to_bytes();
664 serializer.serialize_bytes(&bytes)
665 }
666 }
667 }
668
669 impl<'de> Deserialize<'de> for MultisigAggregatedSignature {
670 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
671 where
672 D: Deserializer<'de>,
673 {
674 if deserializer.is_human_readable() {
675 let readable = ReadableMultisigAggregatedSignature::deserialize(deserializer)?;
676 if let Some(legacy_bitmap) = &readable.legacy_bitmap {
680 validate_legacy_bitmap(&readable.committee, readable.bitmap, legacy_bitmap)
681 .map_err(serde::de::Error::custom)?;
682 }
683 Ok(Self {
684 signatures: readable.signatures,
685 bitmap: readable.bitmap,
686 legacy_bitmap: readable.legacy_bitmap,
687 committee: readable.committee,
688 })
689 } else {
690 let bytes: Cow<'de, [u8]> = Bytes::deserialize_as(deserializer)?;
691 Self::from_serialized_bytes(bytes)
692 }
693 }
694 }
695
696 impl MultisigAggregatedSignature {
697 pub(crate) fn to_bytes(&self) -> Vec<u8> {
698 let mut buf = Vec::new();
699 buf.push(SignatureScheme::Multisig as u8);
700
701 if let Some(bitmap) = &self.legacy_bitmap {
702 let legacy = LegacyMultisigRef {
703 signatures: &self.signatures,
704 bitmap,
705 committee: LegacyMultisigCommitteeRef {
706 members: &self.committee.members,
707 threshold: self.committee.threshold,
708 },
709 };
710
711 bcs::serialize_into(&mut buf, &legacy).expect("serialization cannot fail");
712 } else {
713 let multisig = MultisigRef {
714 signatures: &self.signatures,
715 bitmap: self.bitmap,
716 committee: &self.committee,
717 };
718 bcs::serialize_into(&mut buf, &multisig).expect("serialization cannot fail");
719 }
720 buf
721 }
722
723 pub(crate) fn from_serialized_bytes<T: AsRef<[u8]>, E: serde::de::Error>(
724 bytes: T,
725 ) -> Result<Self, E> {
726 let bytes = bytes.as_ref();
727 let flag = SignatureScheme::from_byte(
728 *bytes
729 .first()
730 .ok_or_else(|| serde::de::Error::custom("missing signature scheme flag"))?,
731 )
732 .map_err(serde::de::Error::custom)?;
733 if flag != SignatureScheme::Multisig {
734 return Err(serde::de::Error::custom("invalid multisig flag"));
735 }
736 let bcs_bytes = &bytes[1..];
737
738 if let Ok(multisig) = bcs::from_bytes::<Multisig>(bcs_bytes) {
744 Ok(Self {
745 signatures: multisig.signatures,
746 bitmap: multisig.bitmap,
747 legacy_bitmap: None,
748 committee: multisig.committee,
749 })
750 } else if let Ok(legacy) = bcs::from_bytes::<LegacyMultisig>(bcs_bytes) {
751 Ok(Self {
752 signatures: legacy.signatures,
753 bitmap: roaring_bitmap_to_u16(&legacy.bitmap)
754 .map_err(serde::de::Error::custom)?,
755 legacy_bitmap: Some(legacy.bitmap),
756 committee: MultisigCommittee {
757 members: legacy.committee.members,
758 threshold: legacy.committee.threshold,
759 },
760 })
761 } else {
762 Err(serde::de::Error::custom("invalid multisig"))
763 }
764 }
765 }
766
767 #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
768 enum MemberPublicKey {
769 Ed25519(Ed25519PublicKey),
770 Secp256k1(Secp256k1PublicKey),
771 Secp256r1(Secp256r1PublicKey),
772 ZkLogin(ZkLoginPublicIdentifier),
773 Passkey(PasskeyPublicKey),
774 MlDsa65(Box<MlDsa65PublicKey>),
775 }
776
777 #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
778 #[serde(tag = "scheme", rename_all = "lowercase")]
779 #[serde(rename = "MultisigMemberPublicKey")]
780 enum ReadableMemberPublicKey {
781 Ed25519 { public_key: Ed25519PublicKey },
782 Secp256k1 { public_key: Secp256k1PublicKey },
783 Secp256r1 { public_key: Secp256r1PublicKey },
784 ZkLogin(ZkLoginPublicIdentifier),
785 Passkey { public_key: PasskeyPublicKey },
786 MlDsa65 { public_key: Box<MlDsa65PublicKey> },
787 }
788
789 impl Serialize for MultisigMemberPublicKey {
790 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
791 where
792 S: Serializer,
793 {
794 if serializer.is_human_readable() {
795 let readable = match self {
796 MultisigMemberPublicKey::Ed25519(public_key) => {
797 ReadableMemberPublicKey::Ed25519 {
798 public_key: *public_key,
799 }
800 }
801 MultisigMemberPublicKey::Secp256k1(public_key) => {
802 ReadableMemberPublicKey::Secp256k1 {
803 public_key: *public_key,
804 }
805 }
806 MultisigMemberPublicKey::Secp256r1(public_key) => {
807 ReadableMemberPublicKey::Secp256r1 {
808 public_key: *public_key,
809 }
810 }
811 MultisigMemberPublicKey::ZkLogin(public_id) => {
812 ReadableMemberPublicKey::ZkLogin(public_id.clone())
813 }
814 MultisigMemberPublicKey::Passkey(public_key) => {
815 ReadableMemberPublicKey::Passkey {
816 public_key: *public_key,
817 }
818 }
819 MultisigMemberPublicKey::MlDsa65(public_key) => {
820 ReadableMemberPublicKey::MlDsa65 {
821 public_key: public_key.clone(),
822 }
823 }
824 };
825 readable.serialize(serializer)
826 } else {
827 let binary = match self {
828 MultisigMemberPublicKey::Ed25519(public_key) => {
829 MemberPublicKey::Ed25519(*public_key)
830 }
831 MultisigMemberPublicKey::Secp256k1(public_key) => {
832 MemberPublicKey::Secp256k1(*public_key)
833 }
834 MultisigMemberPublicKey::Secp256r1(public_key) => {
835 MemberPublicKey::Secp256r1(*public_key)
836 }
837 MultisigMemberPublicKey::ZkLogin(public_id) => {
838 MemberPublicKey::ZkLogin(public_id.clone())
839 }
840 MultisigMemberPublicKey::Passkey(public_key) => {
841 MemberPublicKey::Passkey(*public_key)
842 }
843 MultisigMemberPublicKey::MlDsa65(public_key) => {
844 MemberPublicKey::MlDsa65(public_key.clone())
845 }
846 };
847 binary.serialize(serializer)
848 }
849 }
850 }
851
852 impl<'de> Deserialize<'de> for MultisigMemberPublicKey {
853 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
854 where
855 D: Deserializer<'de>,
856 {
857 if deserializer.is_human_readable() {
858 let readable = ReadableMemberPublicKey::deserialize(deserializer)?;
859 Ok(match readable {
860 ReadableMemberPublicKey::Ed25519 { public_key } => Self::Ed25519(public_key),
861 ReadableMemberPublicKey::Secp256k1 { public_key } => {
862 Self::Secp256k1(public_key)
863 }
864 ReadableMemberPublicKey::Secp256r1 { public_key } => {
865 Self::Secp256r1(public_key)
866 }
867 ReadableMemberPublicKey::ZkLogin(public_id) => Self::ZkLogin(public_id),
868 ReadableMemberPublicKey::Passkey { public_key } => Self::Passkey(public_key),
869 ReadableMemberPublicKey::MlDsa65 { public_key } => Self::MlDsa65(public_key),
870 })
871 } else {
872 let binary = MemberPublicKey::deserialize(deserializer)?;
873 Ok(match binary {
874 MemberPublicKey::Ed25519(public_key) => Self::Ed25519(public_key),
875 MemberPublicKey::Secp256k1(public_key) => Self::Secp256k1(public_key),
876 MemberPublicKey::Secp256r1(public_key) => Self::Secp256r1(public_key),
877 MemberPublicKey::ZkLogin(public_id) => Self::ZkLogin(public_id),
878 MemberPublicKey::Passkey(public_key) => Self::Passkey(public_key),
879 MemberPublicKey::MlDsa65(public_key) => Self::MlDsa65(public_key),
880 })
881 }
882 }
883 }
884
885 #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
886 enum MemberSignature {
887 Ed25519(Ed25519Signature),
888 Secp256k1(Secp256k1Signature),
889 Secp256r1(Secp256r1Signature),
890 ZkLogin(Box<ZkLoginAuthenticator>),
891 Passkey(PasskeyAuthenticator),
892 MlDsa65(Box<MlDsa65Signature>),
893 }
894
895 #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
896 #[serde(tag = "scheme", rename_all = "lowercase")]
897 #[serde(rename = "MultisigMemberSignature")]
898 enum ReadableMemberSignature {
899 Ed25519 { signature: Ed25519Signature },
900 Secp256k1 { signature: Secp256k1Signature },
901 Secp256r1 { signature: Secp256r1Signature },
902 ZkLogin(Box<ZkLoginAuthenticator>),
903 Passkey(PasskeyAuthenticator),
904 MlDsa65 { signature: Box<MlDsa65Signature> },
905 }
906
907 impl Serialize for MultisigMemberSignature {
908 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
909 where
910 S: Serializer,
911 {
912 if serializer.is_human_readable() {
913 let readable = match self {
914 MultisigMemberSignature::Ed25519(signature) => {
915 ReadableMemberSignature::Ed25519 {
916 signature: *signature,
917 }
918 }
919 MultisigMemberSignature::Secp256k1(signature) => {
920 ReadableMemberSignature::Secp256k1 {
921 signature: *signature,
922 }
923 }
924 MultisigMemberSignature::Secp256r1(signature) => {
925 ReadableMemberSignature::Secp256r1 {
926 signature: *signature,
927 }
928 }
929 MultisigMemberSignature::ZkLogin(authenticator) => {
930 ReadableMemberSignature::ZkLogin(authenticator.clone())
931 }
932 MultisigMemberSignature::Passkey(authenticator) => {
933 ReadableMemberSignature::Passkey(authenticator.clone())
934 }
935 MultisigMemberSignature::MlDsa65(signature) => {
936 ReadableMemberSignature::MlDsa65 {
937 signature: signature.clone(),
938 }
939 }
940 };
941 readable.serialize(serializer)
942 } else {
943 let binary = match self {
944 MultisigMemberSignature::Ed25519(signature) => {
945 MemberSignature::Ed25519(*signature)
946 }
947 MultisigMemberSignature::Secp256k1(signature) => {
948 MemberSignature::Secp256k1(*signature)
949 }
950 MultisigMemberSignature::Secp256r1(signature) => {
951 MemberSignature::Secp256r1(*signature)
952 }
953 MultisigMemberSignature::ZkLogin(authenticator) => {
954 MemberSignature::ZkLogin(authenticator.clone())
955 }
956 MultisigMemberSignature::Passkey(authenticator) => {
957 MemberSignature::Passkey(authenticator.clone())
958 }
959 MultisigMemberSignature::MlDsa65(signature) => {
960 MemberSignature::MlDsa65(signature.clone())
961 }
962 };
963 binary.serialize(serializer)
964 }
965 }
966 }
967
968 impl<'de> Deserialize<'de> for MultisigMemberSignature {
969 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
970 where
971 D: Deserializer<'de>,
972 {
973 if deserializer.is_human_readable() {
974 let readable = ReadableMemberSignature::deserialize(deserializer)?;
975 Ok(match readable {
976 ReadableMemberSignature::Ed25519 { signature } => Self::Ed25519(signature),
977 ReadableMemberSignature::Secp256k1 { signature } => Self::Secp256k1(signature),
978 ReadableMemberSignature::Secp256r1 { signature } => Self::Secp256r1(signature),
979 ReadableMemberSignature::ZkLogin(authenticator) => Self::ZkLogin(authenticator),
980 ReadableMemberSignature::Passkey(authenticator) => Self::Passkey(authenticator),
981 ReadableMemberSignature::MlDsa65 { signature } => Self::MlDsa65(signature),
982 })
983 } else {
984 let binary = MemberSignature::deserialize(deserializer)?;
985 Ok(match binary {
986 MemberSignature::Ed25519(signature) => Self::Ed25519(signature),
987 MemberSignature::Secp256k1(signature) => Self::Secp256k1(signature),
988 MemberSignature::Secp256r1(signature) => Self::Secp256r1(signature),
989 MemberSignature::ZkLogin(authenticator) => Self::ZkLogin(authenticator),
990 MemberSignature::Passkey(authenticator) => Self::Passkey(authenticator),
991 MemberSignature::MlDsa65(signature) => Self::MlDsa65(signature),
992 })
993 }
994 }
995 }
996}
997
998#[cfg(test)]
999mod test {
1000 use super::*;
1001
1002 #[cfg(target_arch = "wasm32")]
1003 use wasm_bindgen_test::wasm_bindgen_test as test;
1004
1005 #[test]
1010 fn partial_eq_includes_legacy_bitmap() {
1011 let committee = MultisigCommittee::new(Vec::new(), 0);
1012 let a = MultisigAggregatedSignature::new(committee.clone(), Vec::new(), 0);
1013 let mut b = MultisigAggregatedSignature::new(committee, Vec::new(), 0);
1014 assert_eq!(a, b);
1015
1016 b.try_with_legacy_bitmap(crate::Bitmap::new()).unwrap();
1017 assert_ne!(a, b);
1018 }
1019
1020 fn ed25519_member(byte: u8) -> MultisigMember {
1021 MultisigMember::new(
1022 MultisigMemberPublicKey::Ed25519(Ed25519PublicKey::new(
1023 [byte; Ed25519PublicKey::LENGTH],
1024 )),
1025 1,
1026 )
1027 }
1028
1029 #[test]
1030 fn try_with_legacy_bitmap_rejects_mismatched_bitmap() {
1031 let committee = MultisigCommittee::new(vec![ed25519_member(1), ed25519_member(2)], 1);
1032 let mut signature = MultisigAggregatedSignature::new(committee, Vec::new(), 0b10);
1033
1034 let err = signature
1035 .try_with_legacy_bitmap([0].into_iter().collect())
1036 .unwrap_err();
1037 assert_eq!(err.to_string(), "bitmap does not match legacy_bitmap");
1038
1039 let err = signature
1040 .try_with_legacy_bitmap([MAX_COMMITTEE_SIZE as u32].into_iter().collect())
1041 .unwrap_err();
1042 assert_eq!(err.to_string(), "invalid bitmap");
1043
1044 assert_eq!(signature.legacy_bitmap(), None);
1045
1046 let legacy_bitmap: crate::Bitmap = [1].into_iter().collect();
1047 signature
1048 .try_with_legacy_bitmap(legacy_bitmap.clone())
1049 .unwrap();
1050 assert_eq!(signature.legacy_bitmap(), Some(&legacy_bitmap));
1051 }
1052
1053 #[test]
1056 fn try_with_legacy_bitmap_rejects_unrepresentable_members() {
1057 let zklogin = MultisigMemberPublicKey::ZkLogin(
1058 ZkLoginPublicIdentifier::new(
1059 "https://accounts.google.com".to_owned(),
1060 "7".parse().unwrap(),
1061 )
1062 .unwrap(),
1063 );
1064 let passkey = MultisigMemberPublicKey::Passkey(PasskeyPublicKey::new(
1065 Secp256r1PublicKey::new([2; Secp256r1PublicKey::LENGTH]),
1066 ));
1067 let mldsa65 = MultisigMemberPublicKey::MlDsa65(Box::new(MlDsa65PublicKey::new(
1068 [0; MlDsa65PublicKey::LENGTH],
1069 )));
1070
1071 for (public_key, scheme) in [
1072 (zklogin, "zklogin"),
1073 (passkey, "passkey"),
1074 (mldsa65, "mldsa65"),
1075 ] {
1076 let committee = MultisigCommittee::new(
1077 vec![ed25519_member(1), MultisigMember::new(public_key, 1)],
1078 1,
1079 );
1080 let mut signature = MultisigAggregatedSignature::new(committee, Vec::new(), 0b1);
1081
1082 let err = signature
1083 .try_with_legacy_bitmap([0].into_iter().collect())
1084 .unwrap_err();
1085 assert_eq!(
1086 err.to_string(),
1087 format!("{scheme} member is not representable in legacy multisig")
1088 );
1089 assert_eq!(signature.legacy_bitmap(), None);
1090 }
1091 }
1092
1093 #[cfg(feature = "serde")]
1094 #[test]
1095 fn try_with_legacy_bitmap_round_trips_through_bcs() {
1096 let committee = MultisigCommittee::new(vec![ed25519_member(1), ed25519_member(2)], 1);
1097 let mut signature = MultisigAggregatedSignature::new(
1098 committee,
1099 vec![MultisigMemberSignature::Ed25519(Ed25519Signature::new(
1100 [3; Ed25519Signature::LENGTH],
1101 ))],
1102 0b10,
1103 );
1104 signature
1105 .try_with_legacy_bitmap([1].into_iter().collect())
1106 .unwrap();
1107
1108 let signature = crate::UserSignature::Multisig(signature);
1109 let bytes = signature.to_bytes();
1110 assert_eq!(crate::UserSignature::from_bytes(&bytes).unwrap(), signature);
1111 }
1112
1113 #[cfg(feature = "serde")]
1121 #[test]
1122 fn json_dual_bitmap_must_be_consistent() {
1123 let mut roaring = crate::Bitmap::new();
1124 roaring.insert(5);
1125 let legacy_b64 = {
1126 use base64ct::Encoding;
1127 let mut buf = Vec::new();
1128 roaring.serialize_into(&mut buf).unwrap();
1129 base64ct::Base64::encode_string(&buf)
1130 };
1131
1132 let inconsistent = format!(
1134 r#"{{"signatures":[],"bitmap":1,"legacy_bitmap":"{legacy_b64}",
1135 "committee":{{"members":[],"threshold":0}}}}"#
1136 );
1137 let err = serde_json::from_str::<MultisigAggregatedSignature>(&inconsistent)
1138 .expect_err("inconsistent dual bitmap must be rejected");
1139 assert!(
1140 err.to_string().contains("legacy_bitmap"),
1141 "unexpected error: {err}"
1142 );
1143
1144 let consistent = format!(
1147 r#"{{"signatures":[],"bitmap":{},"legacy_bitmap":"{legacy_b64}",
1148 "committee":{{"members":[],"threshold":0}}}}"#,
1149 1u16 << 5,
1150 );
1151 serde_json::from_str::<MultisigAggregatedSignature>(&consistent)
1152 .expect("consistent dual bitmap must be accepted");
1153 }
1154
1155 #[cfg(feature = "serde")]
1163 #[test]
1164 fn json_legacy_bitmap_with_zklogin_member_is_rejected() {
1165 let legacy_b64 = {
1166 use base64ct::Encoding;
1167 let mut buf = Vec::new();
1168 crate::Bitmap::new().serialize_into(&mut buf).unwrap();
1169 base64ct::Base64::encode_string(&buf)
1170 };
1171
1172 let payload = format!(
1173 r#"{{
1174 "signatures":[],
1175 "bitmap":0,
1176 "legacy_bitmap":"{legacy_b64}",
1177 "committee":{{
1178 "members":[{{
1179 "public_key":{{
1180 "scheme":"zklogin",
1181 "iss":"https://accounts.google.com",
1182 "address_seed":"7"
1183 }},
1184 "weight":1
1185 }}],
1186 "threshold":1
1187 }}
1188 }}"#
1189 );
1190 let err = serde_json::from_str::<MultisigAggregatedSignature>(&payload)
1191 .expect_err("zklogin member with legacy bitmap must be rejected");
1192 assert!(
1193 err.to_string().contains("zklogin"),
1194 "unexpected error: {err}"
1195 );
1196 }
1197}