sui_sdk_types/crypto/
multisig.rs

1use super::Ed25519PublicKey;
2use super::Ed25519Signature;
3use super::PasskeyAuthenticator;
4use super::PasskeyPublicKey;
5use super::Secp256k1PublicKey;
6use super::Secp256k1Signature;
7use super::Secp256r1PublicKey;
8use super::Secp256r1Signature;
9use super::SignatureScheme;
10use super::zklogin::ZkLoginAuthenticator;
11use super::zklogin::ZkLoginPublicIdentifier;
12
13pub type WeightUnit = u8;
14pub type ThresholdUnit = u16;
15pub type BitmapUnit = u16;
16
17const MAX_COMMITTEE_SIZE: usize = 10;
18// TODO validate sigs
19// const MAX_BITMAP_VALUE: BitmapUnit = 0b1111111111;
20
21/// Enum of valid public keys for multisig committee members
22///
23/// # BCS
24///
25/// The BCS serialized form for this type is defined by the following ABNF:
26///
27/// ```text
28/// multisig-member-public-key = ed25519-multisig-member-public-key /
29///                              secp256k1-multisig-member-public-key /
30///                              secp256r1-multisig-member-public-key /
31///                              zklogin-multisig-member-public-key
32///
33/// ed25519-multisig-member-public-key   = %x00 ed25519-public-key
34/// secp256k1-multisig-member-public-key = %x01 secp256k1-public-key
35/// secp256r1-multisig-member-public-key = %x02 secp256r1-public-key
36/// zklogin-multisig-member-public-key   = %x03 zklogin-public-identifier
37/// ```
38///
39/// There is also a legacy encoding for this type defined as:
40///
41/// ```text
42/// legacy-multisig-member-public-key = string ; which is valid base64 encoded
43///                                            ; and the decoded bytes are defined
44///                                            ; by legacy-public-key
45/// legacy-public-key = (ed25519-flag ed25519-public-key) /
46///                     (secp256k1-flag secp256k1-public-key) /
47///                     (secp256r1-flag secp256r1-public-key)
48/// ```
49#[derive(Clone, Debug, PartialEq, Eq)]
50#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
51#[non_exhaustive]
52pub enum MultisigMemberPublicKey {
53    Ed25519(Ed25519PublicKey),
54    Secp256k1(Secp256k1PublicKey),
55    Secp256r1(Secp256r1PublicKey),
56    ZkLogin(ZkLoginPublicIdentifier),
57    Passkey(PasskeyPublicKey),
58}
59
60/// A member in a multisig committee
61///
62/// # BCS
63///
64/// The BCS serialized form for this type is defined by the following ABNF:
65///
66/// ```text
67/// multisig-member = multisig-member-public-key
68///                   u8    ; weight
69/// ```
70///
71/// There is also a legacy encoding for this type defined as:
72///
73/// ```text
74/// legacy-multisig-member = legacy-multisig-member-public-key
75///                          u8     ; weight
76/// ```
77#[derive(Clone, Debug, PartialEq, Eq)]
78#[cfg_attr(
79    feature = "serde",
80    derive(serde_derive::Serialize, serde_derive::Deserialize)
81)]
82#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
83pub struct MultisigMember {
84    public_key: MultisigMemberPublicKey,
85    weight: WeightUnit,
86}
87
88impl MultisigMember {
89    /// Construct a new member from a `MultisigMemberPublicKey` and a `weight`.
90    pub fn new(public_key: MultisigMemberPublicKey, weight: WeightUnit) -> Self {
91        Self { public_key, weight }
92    }
93
94    /// This member's public key.
95    pub fn public_key(&self) -> &MultisigMemberPublicKey {
96        &self.public_key
97    }
98
99    /// Weight of this member's signature.
100    pub fn weight(&self) -> WeightUnit {
101        self.weight
102    }
103}
104
105/// A multisig committee
106///
107/// A `MultisigCommittee` is a set of members who collectively control a single `Address` on the
108/// Sui blockchain. The number of required signautres to authorize the execution of a transaction
109/// is determined by `(signature_0_weight + signature_1_weight ..) >= threshold`.
110///
111/// # Validity
112///
113/// Deserialization (BCS, JSON, or `from_serialized_bytes` on a containing
114/// `MultisigAggregatedSignature`) does **not** enforce structural validity:
115/// the resulting committee may have zero members, zero threshold, threshold
116/// greater than the sum of weights, duplicate members, or more than the
117/// `MAX_COMMITTEE_SIZE` limit. Validity is checked downstream by the
118/// verifier in `sui-crypto` before any signature is verified.
119///
120/// Consumers who inspect a deserialized committee — counting members,
121/// summing weights, indexing by bitmap, etc. — without first running
122/// signature verification **must** call [`MultisigCommittee::is_valid`]
123/// and reject the committee if it returns `false`. Skipping this check
124/// can cause downstream code to operate on attacker-supplied,
125/// well-formed-looking but malformed committees.
126///
127/// # BCS
128///
129/// The BCS serialized form for this type is defined by the following ABNF:
130///
131/// ```text
132/// multisig-committee = (vector multisig-member)
133///                      u16    ; threshold
134/// ```
135///
136/// There is also a legacy encoding for this type defined as:
137///
138/// ```text
139/// legacy-multisig-committee = (vector legacy-multisig-member)
140///                             u16     ; threshold
141/// ```
142#[derive(Debug, Clone, PartialEq, Eq)]
143#[cfg_attr(
144    feature = "serde",
145    derive(serde_derive::Serialize, serde_derive::Deserialize)
146)]
147#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
148pub struct MultisigCommittee {
149    /// A list of committee members and their corresponding weight.
150    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=10).lift()))]
151    members: Vec<MultisigMember>,
152
153    /// If the total weight of the public keys corresponding to verified signatures is larger than
154    /// threshold, the Multisig is verified.
155    threshold: ThresholdUnit,
156}
157
158impl MultisigCommittee {
159    /// Construct a new committee from a list of `MultisigMember`s and a `threshold`.
160    ///
161    /// Note that the order of the members is significant towards deriving the `Address` governed
162    /// by this committee.
163    pub fn new(members: Vec<MultisigMember>, threshold: ThresholdUnit) -> Self {
164        Self { members, threshold }
165    }
166
167    /// The members of the committee
168    pub fn members(&self) -> &[MultisigMember] {
169        &self.members
170    }
171
172    /// The total signature weight required to authorize a transaction for the address
173    /// corresponding to this `MultisigCommittee`.
174    pub fn threshold(&self) -> ThresholdUnit {
175        self.threshold
176    }
177
178    /// Return the flag for this signature scheme
179    pub fn scheme(&self) -> SignatureScheme {
180        SignatureScheme::Multisig
181    }
182
183    /// Checks if the Committee is valid.
184    ///
185    /// A valid committee is one that:
186    ///  - Has a nonzero threshold
187    ///  - Has at least one member
188    ///  - Has at most ten members
189    ///  - No member has weight 0
190    ///  - the sum of the weights of all members must be larger than the threshold
191    ///  - contains no duplicate members
192    pub fn is_valid(&self) -> bool {
193        self.threshold != 0
194            && !self.members.is_empty()
195            && self.members.len() <= MAX_COMMITTEE_SIZE
196            && !self.members.iter().any(|member| member.weight == 0)
197            && self
198                .members
199                .iter()
200                .map(|member| member.weight as ThresholdUnit)
201                .sum::<ThresholdUnit>()
202                >= self.threshold
203            && !self.members.iter().enumerate().any(|(i, member)| {
204                self.members
205                    .iter()
206                    .skip(i + 1)
207                    .any(|m| member.public_key == m.public_key)
208            })
209    }
210}
211
212/// Aggregated signature from members of a multisig committee.
213///
214/// # BCS
215///
216/// The BCS serialized form for this type is defined by the following ABNF:
217///
218/// ```text
219/// multisig-aggregated-signature = (vector multisig-member-signature)
220///                                 u16     ; bitmap
221///                                 multisig-committee
222/// ```
223///
224/// There is also a legacy encoding for this type defined as:
225///
226/// ```text
227/// legacy-multisig-aggregated-signature = (vector multisig-member-signature)
228///                                        roaring-bitmap   ; bitmap
229///                                        legacy-multisig-committee
230/// roaring-bitmap = bytes  ; where the contents of the bytes are valid
231///                         ; according to the serialized spec for
232///                         ; roaring bitmaps
233/// ```
234///
235/// See [here](https://github.com/RoaringBitmap/RoaringFormatSpec) for the specification for the
236/// serialized format of RoaringBitmaps.
237#[derive(Debug, Clone)]
238#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
239pub struct MultisigAggregatedSignature {
240    /// The plain signature encoded with signature scheme.
241    ///
242    /// The signatures must be in the same order as they are listed in the committee.
243    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=10).lift()))]
244    signatures: Vec<MultisigMemberSignature>,
245    /// A bitmap that indicates the position of which public key the signature should be
246    /// authenticated with.
247    bitmap: BitmapUnit,
248    /// Legacy encoding for the bitmap.
249    //TODO implement a strategy for legacy bitmap
250    #[cfg_attr(feature = "proptest", strategy(proptest::strategy::Just(None)))]
251    legacy_bitmap: Option<crate::Bitmap>,
252    /// The public key encoded with each public key with its signature scheme used along with the
253    /// corresponding weight.
254    committee: MultisigCommittee,
255}
256
257impl MultisigAggregatedSignature {
258    /// Construct a new aggregated multisig signature.
259    ///
260    /// Since the list of signatures doesn't contain sufficient information to identify which
261    /// committee member provided the signature, it is up to the caller to ensure that the provided
262    /// signature list is in the same order as it's corresponding member in the provided committee
263    /// and that it's position in the provided bitmap is set.
264    pub fn new(
265        committee: MultisigCommittee,
266        signatures: Vec<MultisigMemberSignature>,
267        bitmap: BitmapUnit,
268    ) -> Self {
269        Self {
270            signatures,
271            bitmap,
272            legacy_bitmap: None,
273            committee,
274        }
275    }
276
277    /// The list of signatures from committee members
278    pub fn signatures(&self) -> &[MultisigMemberSignature] {
279        &self.signatures
280    }
281
282    /// The bitmap that indicates which committee members provided their signature.
283    pub fn bitmap(&self) -> BitmapUnit {
284        self.bitmap
285    }
286
287    /// The legacy roaring bitmap, if this is a legacy formatted signature
288    pub fn legacy_bitmap(&self) -> Option<&crate::Bitmap> {
289        self.legacy_bitmap.as_ref()
290    }
291
292    /// Configure with a legacy roaring bitmap
293    pub fn with_legacy_bitmap(&mut self, legacy_bitmap: crate::Bitmap) {
294        self.legacy_bitmap = Some(legacy_bitmap);
295    }
296
297    /// The committee for this aggregated signature
298    pub fn committee(&self) -> &MultisigCommittee {
299        &self.committee
300    }
301}
302
303impl PartialEq for MultisigAggregatedSignature {
304    fn eq(&self, other: &Self) -> bool {
305        // Compare every field, including `legacy_bitmap`. Although the
306        // legacy bitmap is logically redundant with `bitmap` (they encode
307        // the same information in different formats), `to_bytes` prefers
308        // the legacy form whenever it is `Some`, so two signatures that
309        // differ only by `legacy_bitmap` will serialize to different byte
310        // strings. Excluding `legacy_bitmap` from `==` would let downstream
311        // consumers observe values where `a == b` but
312        // `bcs::to_bytes(a) != bcs::to_bytes(b)`, breaking standard
313        // Eq/Serialize expectations.
314        self.bitmap == other.bitmap
315            && self.legacy_bitmap == other.legacy_bitmap
316            && self.signatures == other.signatures
317            && self.committee == other.committee
318    }
319}
320
321impl Eq for MultisigAggregatedSignature {}
322
323/// Convert a roaring bitmap to plain bitmap.
324#[cfg(feature = "serde")]
325fn roaring_bitmap_to_u16(roaring: &crate::Bitmap) -> Result<BitmapUnit, &'static str> {
326    let mut val = 0;
327    for i in roaring.iter() {
328        if i >= MAX_COMMITTEE_SIZE as u32 {
329            return Err("invalid bitmap");
330        }
331        val |= 1 << i as u8;
332    }
333    Ok(val)
334}
335
336/// A signature from a member of a multisig committee.
337///
338/// # BCS
339///
340/// The BCS serialized form for this type is defined by the following ABNF:
341///
342/// ```text
343/// multisig-member-signature = ed25519-multisig-member-signature /
344///                             secp256k1-multisig-member-signature /
345///                             secp256r1-multisig-member-signature /
346///                             zklogin-multisig-member-signature
347///
348/// ed25519-multisig-member-signature   = %x00 ed25519-signature
349/// secp256k1-multisig-member-signature = %x01 secp256k1-signature
350/// secp256r1-multisig-member-signature = %x02 secp256r1-signature
351/// zklogin-multisig-member-signature   = %x03 zklogin-authenticator
352/// ```
353#[derive(Debug, Clone, PartialEq, Eq)]
354#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
355#[non_exhaustive]
356pub enum MultisigMemberSignature {
357    Ed25519(Ed25519Signature),
358    Secp256k1(Secp256k1Signature),
359    Secp256r1(Secp256r1Signature),
360    ZkLogin(Box<ZkLoginAuthenticator>),
361    Passkey(PasskeyAuthenticator),
362}
363
364#[cfg(feature = "serde")]
365#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
366mod serialization {
367    use super::*;
368    use crate::Ed25519PublicKey;
369    use crate::PasskeyPublicKey;
370    use crate::Secp256k1PublicKey;
371    use crate::Secp256r1PublicKey;
372    use crate::SignatureScheme;
373    use crate::crypto::Base64Array33;
374    use crate::crypto::Base64Array34;
375    use base64ct::Base64;
376    use base64ct::Encoding;
377    use serde::Deserialize;
378    use serde::Deserializer;
379    use serde::Serialize;
380    use serde::Serializer;
381    use serde_with::Bytes;
382    use serde_with::DeserializeAs;
383    use serde_with::SerializeAs;
384    use std::borrow::Cow;
385
386    pub struct Base64MultisigMemberPublicKey;
387
388    impl SerializeAs<MultisigMemberPublicKey> for Base64MultisigMemberPublicKey {
389        fn serialize_as<S>(
390            source: &MultisigMemberPublicKey,
391            serializer: S,
392        ) -> Result<S::Ok, S::Error>
393        where
394            S: Serializer,
395        {
396            match source {
397                MultisigMemberPublicKey::Ed25519(public_key) => {
398                    let mut buf = [0; 1 + Ed25519PublicKey::LENGTH];
399                    buf[0] = SignatureScheme::Ed25519 as u8;
400                    buf[1..].copy_from_slice(public_key.as_ref());
401                    Base64Array33::serialize_as(&buf, serializer)
402                }
403                MultisigMemberPublicKey::Secp256k1(public_key) => {
404                    let mut buf = [0; 1 + Secp256k1PublicKey::LENGTH];
405                    buf[0] = SignatureScheme::Secp256k1 as u8;
406                    buf[1..].copy_from_slice(public_key.as_ref());
407                    Base64Array34::serialize_as(&buf, serializer)
408                }
409                MultisigMemberPublicKey::Secp256r1(public_key) => {
410                    let mut buf = [0; 1 + Secp256r1PublicKey::LENGTH];
411                    buf[0] = SignatureScheme::Secp256r1 as u8;
412                    buf[1..].copy_from_slice(public_key.as_ref());
413                    Base64Array34::serialize_as(&buf, serializer)
414                }
415                MultisigMemberPublicKey::ZkLogin(_) => Err(serde::ser::Error::custom(
416                    "zklogin not supported in legacy multisig",
417                )),
418                MultisigMemberPublicKey::Passkey(_) => Err(serde::ser::Error::custom(
419                    "passkey not supported in legacy multisig",
420                )),
421            }
422        }
423    }
424
425    impl<'de> DeserializeAs<'de, MultisigMemberPublicKey> for Base64MultisigMemberPublicKey {
426        fn deserialize_as<D>(deserializer: D) -> Result<MultisigMemberPublicKey, D::Error>
427        where
428            D: Deserializer<'de>,
429        {
430            let b64: Cow<'de, str> = Deserialize::deserialize(deserializer)?;
431            let bytes = Base64::decode_vec(&b64).map_err(serde::de::Error::custom)?;
432            let flag = SignatureScheme::from_byte(
433                *bytes
434                    .first()
435                    .ok_or_else(|| serde::de::Error::custom("missing signature scheme flag"))?,
436            )
437            .map_err(serde::de::Error::custom)?;
438            let public_key_bytes = &bytes[1..];
439            match flag {
440                SignatureScheme::Ed25519 => {
441                    let public_key = Ed25519PublicKey::from_bytes(public_key_bytes)
442                        .map_err(serde::de::Error::custom)?;
443                    Ok(MultisigMemberPublicKey::Ed25519(public_key))
444                }
445                SignatureScheme::Secp256k1 => {
446                    let public_key = Secp256k1PublicKey::from_bytes(public_key_bytes)
447                        .map_err(serde::de::Error::custom)?;
448                    Ok(MultisigMemberPublicKey::Secp256k1(public_key))
449                }
450                SignatureScheme::Secp256r1 => {
451                    let public_key = Secp256r1PublicKey::from_bytes(public_key_bytes)
452                        .map_err(serde::de::Error::custom)?;
453                    Ok(MultisigMemberPublicKey::Secp256r1(public_key))
454                }
455                SignatureScheme::Multisig
456                | SignatureScheme::Bls12381
457                | SignatureScheme::ZkLogin
458                | SignatureScheme::Passkey => {
459                    Err(serde::de::Error::custom("invalid public key type"))
460                }
461            }
462        }
463    }
464
465    pub struct LegacyMultisigMember;
466
467    impl SerializeAs<MultisigMember> for LegacyMultisigMember {
468        fn serialize_as<S>(source: &MultisigMember, serializer: S) -> Result<S::Ok, S::Error>
469        where
470            S: Serializer,
471        {
472            #[derive(serde_derive::Serialize)]
473            struct LegacyMember<'a> {
474                #[serde(with = "::serde_with::As::<Base64MultisigMemberPublicKey>")]
475                public_key: &'a MultisigMemberPublicKey,
476                weight: WeightUnit,
477            }
478
479            let legacy = LegacyMember {
480                public_key: &source.public_key,
481                weight: source.weight,
482            };
483
484            legacy.serialize(serializer)
485        }
486    }
487
488    impl<'de> DeserializeAs<'de, MultisigMember> for LegacyMultisigMember {
489        fn deserialize_as<D>(deserializer: D) -> Result<MultisigMember, D::Error>
490        where
491            D: Deserializer<'de>,
492        {
493            #[derive(serde_derive::Deserialize)]
494            struct LegacyMember {
495                #[serde(with = "::serde_with::As::<Base64MultisigMemberPublicKey>")]
496                public_key: MultisigMemberPublicKey,
497                weight: WeightUnit,
498            }
499
500            let legacy = LegacyMember::deserialize(deserializer)?;
501
502            Ok(MultisigMember {
503                public_key: legacy.public_key,
504                weight: legacy.weight,
505            })
506        }
507    }
508
509    #[derive(serde_derive::Deserialize)]
510    pub struct Multisig {
511        signatures: Vec<MultisigMemberSignature>,
512        bitmap: BitmapUnit,
513        committee: MultisigCommittee,
514    }
515
516    #[derive(serde_derive::Serialize)]
517    pub struct MultisigRef<'a> {
518        signatures: &'a [MultisigMemberSignature],
519        bitmap: BitmapUnit,
520        committee: &'a MultisigCommittee,
521    }
522
523    #[derive(serde_derive::Deserialize)]
524    pub struct LegacyMultisig {
525        signatures: Vec<MultisigMemberSignature>,
526        bitmap: crate::Bitmap,
527        committee: LegacyMultisigCommittee,
528    }
529
530    #[derive(serde_derive::Serialize)]
531    pub struct LegacyMultisigRef<'a> {
532        signatures: &'a [MultisigMemberSignature],
533        bitmap: &'a crate::Bitmap,
534        committee: LegacyMultisigCommitteeRef<'a>,
535    }
536
537    #[derive(serde_derive::Deserialize)]
538    struct LegacyMultisigCommittee {
539        #[serde(with = "::serde_with::As::<Vec<LegacyMultisigMember>>")]
540        members: Vec<MultisigMember>,
541        threshold: ThresholdUnit,
542    }
543
544    #[derive(serde_derive::Serialize)]
545    struct LegacyMultisigCommitteeRef<'a> {
546        #[serde(with = "::serde_with::As::<&[LegacyMultisigMember]>")]
547        members: &'a [MultisigMember],
548        threshold: ThresholdUnit,
549    }
550
551    #[derive(serde_derive::Deserialize)]
552    struct ReadableMultisigAggregatedSignature {
553        signatures: Vec<MultisigMemberSignature>,
554        bitmap: BitmapUnit,
555        legacy_bitmap: Option<crate::Bitmap>,
556        committee: MultisigCommittee,
557    }
558
559    #[derive(serde_derive::Serialize)]
560    struct ReadableMultisigAggregatedSignatureRef<'a> {
561        signatures: &'a [MultisigMemberSignature],
562        bitmap: BitmapUnit,
563        #[serde(skip_serializing_if = "Option::is_none")]
564        legacy_bitmap: &'a Option<crate::Bitmap>,
565        committee: &'a MultisigCommittee,
566    }
567
568    impl Serialize for MultisigAggregatedSignature {
569        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
570        where
571            S: Serializer,
572        {
573            if serializer.is_human_readable() {
574                let readable = ReadableMultisigAggregatedSignatureRef {
575                    signatures: &self.signatures,
576                    bitmap: self.bitmap,
577                    legacy_bitmap: &self.legacy_bitmap,
578                    committee: &self.committee,
579                };
580                readable.serialize(serializer)
581            } else {
582                let bytes = self.to_bytes();
583                serializer.serialize_bytes(&bytes)
584            }
585        }
586    }
587
588    impl<'de> Deserialize<'de> for MultisigAggregatedSignature {
589        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
590        where
591            D: Deserializer<'de>,
592        {
593            if deserializer.is_human_readable() {
594                let readable = ReadableMultisigAggregatedSignature::deserialize(deserializer)?;
595                // Mirror the BCS legacy branch's invariant: when
596                // `legacy_bitmap` is present, `bitmap` is derived from it
597                // (`from_serialized_bytes` always rebuilds it via
598                // `roaring_bitmap_to_u16`). Rejecting any other
599                // combination prevents a JSON payload from carrying two
600                // independent signer sets — one observed by `bitmap()`
601                // and another emitted by `to_bytes()` — which would let
602                // an attacker exhibit different attributable signers
603                // through different accessor paths on the same
604                // logical signature.
605                if let Some(legacy_bitmap) = &readable.legacy_bitmap {
606                    let derived =
607                        roaring_bitmap_to_u16(legacy_bitmap).map_err(serde::de::Error::custom)?;
608                    if derived != readable.bitmap {
609                        return Err(serde::de::Error::custom(
610                            "bitmap does not match legacy_bitmap",
611                        ));
612                    }
613                    // The legacy BCS form encodes each member public key
614                    // via `Base64MultisigMemberPublicKey`, which only
615                    // supports Ed25519/Secp256k1/Secp256r1. A
616                    // `legacy_bitmap` attached to a committee with a
617                    // ZkLogin or Passkey member therefore cannot be
618                    // re-serialized: `to_bytes()` would route through
619                    // the legacy branch, hit the explicit `Err` for
620                    // those variants, and panic via the inner
621                    // `.expect("serialization cannot fail")`. Reject the
622                    // combination at deserialization so an untrusted
623                    // JSON payload cannot crash a worker thread on its
624                    // first `to_bytes()`.
625                    for member in &readable.committee.members {
626                        match member.public_key {
627                            MultisigMemberPublicKey::ZkLogin(_) => {
628                                return Err(serde::de::Error::custom(
629                                    "zklogin member is not representable in legacy multisig",
630                                ));
631                            }
632                            MultisigMemberPublicKey::Passkey(_) => {
633                                return Err(serde::de::Error::custom(
634                                    "passkey member is not representable in legacy multisig",
635                                ));
636                            }
637                            MultisigMemberPublicKey::Ed25519(_)
638                            | MultisigMemberPublicKey::Secp256k1(_)
639                            | MultisigMemberPublicKey::Secp256r1(_) => {}
640                        }
641                    }
642                }
643                Ok(Self {
644                    signatures: readable.signatures,
645                    bitmap: readable.bitmap,
646                    legacy_bitmap: readable.legacy_bitmap,
647                    committee: readable.committee,
648                })
649            } else {
650                let bytes: Cow<'de, [u8]> = Bytes::deserialize_as(deserializer)?;
651                Self::from_serialized_bytes(bytes)
652            }
653        }
654    }
655
656    impl MultisigAggregatedSignature {
657        pub(crate) fn to_bytes(&self) -> Vec<u8> {
658            let mut buf = Vec::new();
659            buf.push(SignatureScheme::Multisig as u8);
660
661            if let Some(bitmap) = &self.legacy_bitmap {
662                let legacy = LegacyMultisigRef {
663                    signatures: &self.signatures,
664                    bitmap,
665                    committee: LegacyMultisigCommitteeRef {
666                        members: &self.committee.members,
667                        threshold: self.committee.threshold,
668                    },
669                };
670
671                bcs::serialize_into(&mut buf, &legacy).expect("serialization cannot fail");
672            } else {
673                let multisig = MultisigRef {
674                    signatures: &self.signatures,
675                    bitmap: self.bitmap,
676                    committee: &self.committee,
677                };
678                bcs::serialize_into(&mut buf, &multisig).expect("serialization cannot fail");
679            }
680            buf
681        }
682
683        pub(crate) fn from_serialized_bytes<T: AsRef<[u8]>, E: serde::de::Error>(
684            bytes: T,
685        ) -> Result<Self, E> {
686            let bytes = bytes.as_ref();
687            let flag = SignatureScheme::from_byte(
688                *bytes
689                    .first()
690                    .ok_or_else(|| serde::de::Error::custom("missing signature scheme flag"))?,
691            )
692            .map_err(serde::de::Error::custom)?;
693            if flag != SignatureScheme::Multisig {
694                return Err(serde::de::Error::custom("invalid multisig flag"));
695            }
696            let bcs_bytes = &bytes[1..];
697
698            // Unfortunately we have no information in the serialized form of a Multisig to be
699            // able to determine if its a Legacy format or the new standard format so we just
700            // need to try each.
701            //
702            // We'll start with the newer format as that should be more prevalent.
703            if let Ok(multisig) = bcs::from_bytes::<Multisig>(bcs_bytes) {
704                Ok(Self {
705                    signatures: multisig.signatures,
706                    bitmap: multisig.bitmap,
707                    legacy_bitmap: None,
708                    committee: multisig.committee,
709                })
710            } else if let Ok(legacy) = bcs::from_bytes::<LegacyMultisig>(bcs_bytes) {
711                Ok(Self {
712                    signatures: legacy.signatures,
713                    bitmap: roaring_bitmap_to_u16(&legacy.bitmap)
714                        .map_err(serde::de::Error::custom)?,
715                    legacy_bitmap: Some(legacy.bitmap),
716                    committee: MultisigCommittee {
717                        members: legacy.committee.members,
718                        threshold: legacy.committee.threshold,
719                    },
720                })
721            } else {
722                Err(serde::de::Error::custom("invalid multisig"))
723            }
724        }
725    }
726
727    #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
728    enum MemberPublicKey {
729        Ed25519(Ed25519PublicKey),
730        Secp256k1(Secp256k1PublicKey),
731        Secp256r1(Secp256r1PublicKey),
732        ZkLogin(ZkLoginPublicIdentifier),
733        Passkey(PasskeyPublicKey),
734    }
735
736    #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
737    #[serde(tag = "scheme", rename_all = "lowercase")]
738    #[serde(rename = "MultisigMemberPublicKey")]
739    enum ReadableMemberPublicKey {
740        Ed25519 { public_key: Ed25519PublicKey },
741        Secp256k1 { public_key: Secp256k1PublicKey },
742        Secp256r1 { public_key: Secp256r1PublicKey },
743        ZkLogin(ZkLoginPublicIdentifier),
744        Passkey { public_key: PasskeyPublicKey },
745    }
746
747    impl Serialize for MultisigMemberPublicKey {
748        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
749        where
750            S: Serializer,
751        {
752            if serializer.is_human_readable() {
753                let readable = match self {
754                    MultisigMemberPublicKey::Ed25519(public_key) => {
755                        ReadableMemberPublicKey::Ed25519 {
756                            public_key: *public_key,
757                        }
758                    }
759                    MultisigMemberPublicKey::Secp256k1(public_key) => {
760                        ReadableMemberPublicKey::Secp256k1 {
761                            public_key: *public_key,
762                        }
763                    }
764                    MultisigMemberPublicKey::Secp256r1(public_key) => {
765                        ReadableMemberPublicKey::Secp256r1 {
766                            public_key: *public_key,
767                        }
768                    }
769                    MultisigMemberPublicKey::ZkLogin(public_id) => {
770                        ReadableMemberPublicKey::ZkLogin(public_id.clone())
771                    }
772                    MultisigMemberPublicKey::Passkey(public_key) => {
773                        ReadableMemberPublicKey::Passkey {
774                            public_key: *public_key,
775                        }
776                    }
777                };
778                readable.serialize(serializer)
779            } else {
780                let binary = match self {
781                    MultisigMemberPublicKey::Ed25519(public_key) => {
782                        MemberPublicKey::Ed25519(*public_key)
783                    }
784                    MultisigMemberPublicKey::Secp256k1(public_key) => {
785                        MemberPublicKey::Secp256k1(*public_key)
786                    }
787                    MultisigMemberPublicKey::Secp256r1(public_key) => {
788                        MemberPublicKey::Secp256r1(*public_key)
789                    }
790                    MultisigMemberPublicKey::ZkLogin(public_id) => {
791                        MemberPublicKey::ZkLogin(public_id.clone())
792                    }
793                    MultisigMemberPublicKey::Passkey(public_key) => {
794                        MemberPublicKey::Passkey(*public_key)
795                    }
796                };
797                binary.serialize(serializer)
798            }
799        }
800    }
801
802    impl<'de> Deserialize<'de> for MultisigMemberPublicKey {
803        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
804        where
805            D: Deserializer<'de>,
806        {
807            if deserializer.is_human_readable() {
808                let readable = ReadableMemberPublicKey::deserialize(deserializer)?;
809                Ok(match readable {
810                    ReadableMemberPublicKey::Ed25519 { public_key } => Self::Ed25519(public_key),
811                    ReadableMemberPublicKey::Secp256k1 { public_key } => {
812                        Self::Secp256k1(public_key)
813                    }
814                    ReadableMemberPublicKey::Secp256r1 { public_key } => {
815                        Self::Secp256r1(public_key)
816                    }
817                    ReadableMemberPublicKey::ZkLogin(public_id) => Self::ZkLogin(public_id),
818                    ReadableMemberPublicKey::Passkey { public_key } => Self::Passkey(public_key),
819                })
820            } else {
821                let binary = MemberPublicKey::deserialize(deserializer)?;
822                Ok(match binary {
823                    MemberPublicKey::Ed25519(public_key) => Self::Ed25519(public_key),
824                    MemberPublicKey::Secp256k1(public_key) => Self::Secp256k1(public_key),
825                    MemberPublicKey::Secp256r1(public_key) => Self::Secp256r1(public_key),
826                    MemberPublicKey::ZkLogin(public_id) => Self::ZkLogin(public_id),
827                    MemberPublicKey::Passkey(public_key) => Self::Passkey(public_key),
828                })
829            }
830        }
831    }
832
833    #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
834    enum MemberSignature {
835        Ed25519(Ed25519Signature),
836        Secp256k1(Secp256k1Signature),
837        Secp256r1(Secp256r1Signature),
838        ZkLogin(Box<ZkLoginAuthenticator>),
839        Passkey(PasskeyAuthenticator),
840    }
841
842    #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
843    #[serde(tag = "scheme", rename_all = "lowercase")]
844    #[serde(rename = "MultisigMemberSignature")]
845    enum ReadableMemberSignature {
846        Ed25519 { signature: Ed25519Signature },
847        Secp256k1 { signature: Secp256k1Signature },
848        Secp256r1 { signature: Secp256r1Signature },
849        ZkLogin(Box<ZkLoginAuthenticator>),
850        Passkey(PasskeyAuthenticator),
851    }
852
853    impl Serialize for MultisigMemberSignature {
854        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
855        where
856            S: Serializer,
857        {
858            if serializer.is_human_readable() {
859                let readable = match self {
860                    MultisigMemberSignature::Ed25519(signature) => {
861                        ReadableMemberSignature::Ed25519 {
862                            signature: *signature,
863                        }
864                    }
865                    MultisigMemberSignature::Secp256k1(signature) => {
866                        ReadableMemberSignature::Secp256k1 {
867                            signature: *signature,
868                        }
869                    }
870                    MultisigMemberSignature::Secp256r1(signature) => {
871                        ReadableMemberSignature::Secp256r1 {
872                            signature: *signature,
873                        }
874                    }
875                    MultisigMemberSignature::ZkLogin(authenticator) => {
876                        ReadableMemberSignature::ZkLogin(authenticator.clone())
877                    }
878                    MultisigMemberSignature::Passkey(authenticator) => {
879                        ReadableMemberSignature::Passkey(authenticator.clone())
880                    }
881                };
882                readable.serialize(serializer)
883            } else {
884                let binary = match self {
885                    MultisigMemberSignature::Ed25519(signature) => {
886                        MemberSignature::Ed25519(*signature)
887                    }
888                    MultisigMemberSignature::Secp256k1(signature) => {
889                        MemberSignature::Secp256k1(*signature)
890                    }
891                    MultisigMemberSignature::Secp256r1(signature) => {
892                        MemberSignature::Secp256r1(*signature)
893                    }
894                    MultisigMemberSignature::ZkLogin(authenticator) => {
895                        MemberSignature::ZkLogin(authenticator.clone())
896                    }
897                    MultisigMemberSignature::Passkey(authenticator) => {
898                        MemberSignature::Passkey(authenticator.clone())
899                    }
900                };
901                binary.serialize(serializer)
902            }
903        }
904    }
905
906    impl<'de> Deserialize<'de> for MultisigMemberSignature {
907        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
908        where
909            D: Deserializer<'de>,
910        {
911            if deserializer.is_human_readable() {
912                let readable = ReadableMemberSignature::deserialize(deserializer)?;
913                Ok(match readable {
914                    ReadableMemberSignature::Ed25519 { signature } => Self::Ed25519(signature),
915                    ReadableMemberSignature::Secp256k1 { signature } => Self::Secp256k1(signature),
916                    ReadableMemberSignature::Secp256r1 { signature } => Self::Secp256r1(signature),
917                    ReadableMemberSignature::ZkLogin(authenticator) => Self::ZkLogin(authenticator),
918                    ReadableMemberSignature::Passkey(authenticator) => Self::Passkey(authenticator),
919                })
920            } else {
921                let binary = MemberSignature::deserialize(deserializer)?;
922                Ok(match binary {
923                    MemberSignature::Ed25519(signature) => Self::Ed25519(signature),
924                    MemberSignature::Secp256k1(signature) => Self::Secp256k1(signature),
925                    MemberSignature::Secp256r1(signature) => Self::Secp256r1(signature),
926                    MemberSignature::ZkLogin(authenticator) => Self::ZkLogin(authenticator),
927                    MemberSignature::Passkey(authenticator) => Self::Passkey(authenticator),
928                })
929            }
930        }
931    }
932}
933
934#[cfg(test)]
935mod test {
936    use super::*;
937
938    #[cfg(target_arch = "wasm32")]
939    use wasm_bindgen_test::wasm_bindgen_test as test;
940
941    // Regression test: `legacy_bitmap` used to be excluded from
942    // `PartialEq`, so two signatures whose `to_bytes` output differed
943    // (because the legacy form is preferred when present) could compare
944    // equal. Equality must now imply byte equality.
945    #[test]
946    fn partial_eq_includes_legacy_bitmap() {
947        let committee = MultisigCommittee::new(Vec::new(), 0);
948        let a = MultisigAggregatedSignature::new(committee.clone(), Vec::new(), 0);
949        let mut b = MultisigAggregatedSignature::new(committee, Vec::new(), 0);
950        assert_eq!(a, b);
951
952        b.with_legacy_bitmap(crate::Bitmap::new());
953        assert_ne!(a, b);
954    }
955
956    // Regression test: the JSON deserializer used to copy `bitmap` and
957    // `legacy_bitmap` straight onto the value without checking that
958    // they encoded the same signer set, letting a single payload carry
959    // one signer set observed by `bitmap()` and a different one emitted
960    // by `to_bytes()` (which prefers the legacy form when present). The
961    // BCS legacy branch always derives `bitmap` from `legacy_bitmap`, so
962    // the JSON path must reject inputs where those two fields disagree.
963    #[cfg(feature = "serde")]
964    #[test]
965    fn json_dual_bitmap_must_be_consistent() {
966        let mut roaring = crate::Bitmap::new();
967        roaring.insert(5);
968        let legacy_b64 = {
969            use base64ct::Encoding;
970            let mut buf = Vec::new();
971            roaring.serialize_into(&mut buf).unwrap();
972            base64ct::Base64::encode_string(&buf)
973        };
974
975        // `bitmap` claims signer 0, `legacy_bitmap` claims signer 5.
976        let inconsistent = format!(
977            r#"{{"signatures":[],"bitmap":1,"legacy_bitmap":"{legacy_b64}",
978                "committee":{{"members":[],"threshold":0}}}}"#
979        );
980        let err = serde_json::from_str::<MultisigAggregatedSignature>(&inconsistent)
981            .expect_err("inconsistent dual bitmap must be rejected");
982        assert!(
983            err.to_string().contains("legacy_bitmap"),
984            "unexpected error: {err}"
985        );
986
987        // The canonical form (bitmap derived from legacy_bitmap) is
988        // accepted.
989        let consistent = format!(
990            r#"{{"signatures":[],"bitmap":{},"legacy_bitmap":"{legacy_b64}",
991                "committee":{{"members":[],"threshold":0}}}}"#,
992            1u16 << 5,
993        );
994        serde_json::from_str::<MultisigAggregatedSignature>(&consistent)
995            .expect("consistent dual bitmap must be accepted");
996    }
997
998    // Regression test: `to_bytes()` used to panic via
999    // `.expect("serialization cannot fail")` when `legacy_bitmap` was
1000    // present alongside a ZkLogin or Passkey committee member, because
1001    // the legacy member encoding (`Base64MultisigMemberPublicKey`)
1002    // explicitly returns `Err` for those variants. The JSON
1003    // deserializer must reject the combination so an attacker cannot
1004    // craft a payload that crashes a consumer on its first `to_bytes()`.
1005    #[cfg(feature = "serde")]
1006    #[test]
1007    fn json_legacy_bitmap_with_zklogin_member_is_rejected() {
1008        let legacy_b64 = {
1009            use base64ct::Encoding;
1010            let mut buf = Vec::new();
1011            crate::Bitmap::new().serialize_into(&mut buf).unwrap();
1012            base64ct::Base64::encode_string(&buf)
1013        };
1014
1015        let payload = format!(
1016            r#"{{
1017                "signatures":[],
1018                "bitmap":0,
1019                "legacy_bitmap":"{legacy_b64}",
1020                "committee":{{
1021                    "members":[{{
1022                        "public_key":{{
1023                            "scheme":"zklogin",
1024                            "iss":"https://accounts.google.com",
1025                            "address_seed":"7"
1026                        }},
1027                        "weight":1
1028                    }}],
1029                    "threshold":1
1030                }}
1031            }}"#
1032        );
1033        let err = serde_json::from_str::<MultisigAggregatedSignature>(&payload)
1034            .expect_err("zklogin member with legacy bitmap must be rejected");
1035        assert!(
1036            err.to_string().contains("zklogin"),
1037            "unexpected error: {err}"
1038        );
1039    }
1040}