iota_sdk_types/crypto/
multisig.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2025 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use super::{
6    Ed25519PublicKey, Ed25519Signature, Secp256k1PublicKey, Secp256k1Signature, Secp256r1PublicKey,
7    Secp256r1Signature, SignatureScheme,
8    zklogin::{ZkLoginAuthenticator, ZkLoginPublicIdentifier},
9};
10
11pub type WeightUnit = u8;
12pub type ThresholdUnit = u16;
13pub type BitmapUnit = u16;
14
15const MAX_COMMITTEE_SIZE: usize = 10;
16// TODO validate sigs
17// const MAX_BITMAP_VALUE: BitmapUnit = 0b1111111111;
18
19/// Enum of valid public keys for multisig committee members
20///
21/// # BCS
22///
23/// The BCS serialized form for this type is defined by the following ABNF:
24///
25/// ```text
26/// multisig-member-public-key = ed25519-multisig-member-public-key /
27///                              secp256k1-multisig-member-public-key /
28///                              secp256r1-multisig-member-public-key /
29///                              zklogin-multisig-member-public-key
30///
31/// ed25519-multisig-member-public-key   = %x00 ed25519-public-key
32/// secp256k1-multisig-member-public-key = %x01 secp256k1-public-key
33/// secp256r1-multisig-member-public-key = %x02 secp256r1-public-key
34/// zklogin-multisig-member-public-key   = %x03 zklogin-public-identifier
35/// ```
36///
37/// There is also a legacy encoding for this type defined as:
38///
39/// ```text
40/// legacy-multisig-member-public-key = string ; which is valid base64 encoded
41///                                            ; and the decoded bytes are defined
42///                                            ; by legacy-public-key
43/// legacy-public-key = (ed25519-flag ed25519-public-key) /
44///                     (secp256k1-flag secp256k1-public-key) /
45///                     (secp256r1-flag secp256r1-public-key)
46/// ```
47#[derive(Clone, Debug, PartialEq, Eq)]
48#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
49pub enum MultisigMemberPublicKey {
50    Ed25519(Ed25519PublicKey),
51    Secp256k1(Secp256k1PublicKey),
52    Secp256r1(Secp256r1PublicKey),
53    ZkLogin(ZkLoginPublicIdentifier),
54}
55
56impl MultisigMemberPublicKey {
57    crate::def_is_as_into_opt!(
58        Ed25519(Ed25519PublicKey),
59        Secp256k1(Secp256k1PublicKey),
60        Secp256r1(Secp256r1PublicKey),
61        ZkLogin as zklogin(ZkLoginPublicIdentifier),
62    );
63}
64
65/// A member in a multisig committee
66///
67/// # BCS
68///
69/// The BCS serialized form for this type is defined by the following ABNF:
70///
71/// ```text
72/// multisig-member = multisig-member-public-key
73///                   u8    ; weight
74/// ```
75///
76/// There is also a legacy encoding for this type defined as:
77///
78/// ```text
79/// legacy-multisig-member = legacy-multisig-member-public-key
80///                          u8     ; weight
81/// ```
82#[derive(Clone, Debug, PartialEq, Eq)]
83#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
84#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
85#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
86pub struct MultisigMember {
87    public_key: MultisigMemberPublicKey,
88    weight: WeightUnit,
89}
90
91impl MultisigMember {
92    /// Construct a new member from a `MultisigMemberPublicKey` and a `weight`.
93    pub fn new(public_key: MultisigMemberPublicKey, weight: WeightUnit) -> Self {
94        Self { public_key, weight }
95    }
96
97    /// This member's public key.
98    pub fn public_key(&self) -> &MultisigMemberPublicKey {
99        &self.public_key
100    }
101
102    /// Weight of this member's signature.
103    pub fn weight(&self) -> WeightUnit {
104        self.weight
105    }
106}
107
108/// A multisig committee
109///
110/// A `MultisigCommittee` is a set of members who collectively control a single
111/// `Address` on the IOTA blockchain. The number of required signatures to
112/// authorize the execution of a transaction is determined by
113/// `(signature_0_weight + signature_1_weight ..) >= threshold`.
114///
115/// # BCS
116///
117/// The BCS serialized form for this type is defined by the following ABNF:
118///
119/// ```text
120/// multisig-committee = (vector multisig-member)
121///                      u16    ; threshold
122/// ```
123///
124/// There is also a legacy encoding for this type defined as:
125///
126/// ```text
127/// legacy-multisig-committee = (vector legacy-multisig-member)
128///                             u16     ; threshold
129/// ```
130#[derive(Debug, Clone, PartialEq, Eq)]
131#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
132#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
133#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
134pub struct MultisigCommittee {
135    /// A list of committee members and their corresponding weight.
136    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=10).lift()))]
137    members: Vec<MultisigMember>,
138    /// If the total weight of the public keys corresponding to verified
139    /// signatures is larger than threshold, the Multisig is verified.
140    threshold: ThresholdUnit,
141}
142
143impl MultisigCommittee {
144    /// Construct a new committee from a list of `MultisigMember`s and a
145    /// `threshold`.
146    ///
147    /// Note that the order of the members is significant towards deriving the
148    /// `Address` governed by this committee.
149    pub fn new(members: Vec<MultisigMember>, threshold: ThresholdUnit) -> Self {
150        Self { members, threshold }
151    }
152
153    /// The members of the committee
154    pub fn members(&self) -> &[MultisigMember] {
155        &self.members
156    }
157
158    /// The total signature weight required to authorize a transaction for the
159    /// address corresponding to this `MultisigCommittee`.
160    pub fn threshold(&self) -> ThresholdUnit {
161        self.threshold
162    }
163
164    /// Return the flag for this signature scheme
165    pub fn scheme(&self) -> SignatureScheme {
166        SignatureScheme::Multisig
167    }
168
169    /// Checks if the Committee is valid.
170    ///
171    /// A valid committee is one that:
172    ///  - Has a nonzero threshold
173    ///  - Has at least one member
174    ///  - Has at most ten members
175    ///  - No member has weight 0
176    ///  - the sum of the weights of all members must be larger than the
177    ///    threshold
178    ///  - contains no duplicate members
179    pub fn is_valid(&self) -> bool {
180        self.threshold != 0
181            && !self.members.is_empty()
182            && self.members.len() <= MAX_COMMITTEE_SIZE
183            && !self.members.iter().any(|member| member.weight == 0)
184            && self
185                .members
186                .iter()
187                .map(|member| member.weight as ThresholdUnit)
188                .sum::<ThresholdUnit>()
189                >= self.threshold
190            && !self.members.iter().enumerate().any(|(i, member)| {
191                self.members
192                    .iter()
193                    .skip(i + 1)
194                    .any(|m| member.public_key == m.public_key)
195            })
196    }
197}
198
199/// Aggregated signature from members of a multisig committee.
200///
201/// # BCS
202///
203/// The BCS serialized form for this type is defined by the following ABNF:
204///
205/// ```text
206/// multisig-aggregated-signature = (vector multisig-member-signature)
207///                                 u16     ; bitmap
208///                                 multisig-committee
209/// ```
210///
211/// There is also a legacy encoding for this type defined as:
212///
213/// ```text
214/// legacy-multisig-aggregated-signature = (vector multisig-member-signature)
215///                                        roaring-bitmap   ; bitmap
216///                                        legacy-multisig-committee
217/// roaring-bitmap = bytes  ; where the contents of the bytes are valid
218///                         ; according to the serialized spec for
219///                         ; roaring bitmaps
220/// ```
221///
222/// See [here](https://github.com/RoaringBitmap/RoaringFormatSpec) for the specification for the
223/// serialized format of RoaringBitmaps.
224#[derive(Debug, Clone)]
225#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
226#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
227pub struct MultisigAggregatedSignature {
228    /// The plain signature encoded with signature scheme.
229    ///
230    /// The signatures must be in the same order as they are listed in the
231    /// committee.
232    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=10).lift()))]
233    signatures: Vec<MultisigMemberSignature>,
234    /// A bitmap that indicates the position of which public key the signature
235    /// should be authenticated with.
236    bitmap: BitmapUnit,
237    /// The public key encoded with each public key with its signature scheme
238    /// used along with the corresponding weight.
239    committee: MultisigCommittee,
240}
241
242impl MultisigAggregatedSignature {
243    /// Construct a new aggregated multisig signature.
244    ///
245    /// Since the list of signatures doesn't contain sufficient information to
246    /// identify which committee member provided the signature, it is up to
247    /// the caller to ensure that the provided signature list is in the same
248    /// order as it's corresponding member in the provided committee
249    /// and that it's position in the provided bitmap is set.
250    pub fn new(
251        committee: MultisigCommittee,
252        signatures: Vec<MultisigMemberSignature>,
253        bitmap: BitmapUnit,
254    ) -> Self {
255        Self {
256            signatures,
257            bitmap,
258            committee,
259        }
260    }
261
262    /// The list of signatures from committee members
263    pub fn signatures(&self) -> &[MultisigMemberSignature] {
264        &self.signatures
265    }
266
267    /// The bitmap that indicates which committee members provided their
268    /// signature.
269    pub fn bitmap(&self) -> BitmapUnit {
270        self.bitmap
271    }
272
273    pub fn committee(&self) -> &MultisigCommittee {
274        &self.committee
275    }
276}
277
278impl PartialEq for MultisigAggregatedSignature {
279    fn eq(&self, other: &Self) -> bool {
280        self.bitmap == other.bitmap
281            && self.signatures == other.signatures
282            && self.committee == other.committee
283    }
284}
285
286impl Eq for MultisigAggregatedSignature {}
287
288/// A signature from a member of a multisig committee.
289///
290/// # BCS
291///
292/// The BCS serialized form for this type is defined by the following ABNF:
293///
294/// ```text
295/// multisig-member-signature = ed25519-multisig-member-signature /
296///                             secp256k1-multisig-member-signature /
297///                             secp256r1-multisig-member-signature /
298///                             zklogin-multisig-member-signature
299///
300/// ed25519-multisig-member-signature   = %x00 ed25519-signature
301/// secp256k1-multisig-member-signature = %x01 secp256k1-signature
302/// secp256r1-multisig-member-signature = %x02 secp256r1-signature
303/// zklogin-multisig-member-signature   = %x03 zklogin-authenticator
304/// ```
305#[derive(Debug, Clone, PartialEq, Eq)]
306#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
307pub enum MultisigMemberSignature {
308    Ed25519(Ed25519Signature),
309    Secp256k1(Secp256k1Signature),
310    Secp256r1(Secp256r1Signature),
311    ZkLogin(Box<ZkLoginAuthenticator>),
312}
313
314impl MultisigMemberSignature {
315    crate::def_is_as_into_opt!(
316        Ed25519(Ed25519Signature),
317        Secp256k1(Secp256k1Signature),
318        Secp256r1(Secp256r1Signature),
319        ZkLogin as zklogin(Box<ZkLoginAuthenticator>)
320    );
321}
322
323#[cfg(feature = "serde")]
324#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
325mod serialization {
326    use std::borrow::Cow;
327
328    use serde::{Deserialize, Deserializer, Serialize, Serializer};
329    use serde_with::{Bytes, DeserializeAs};
330
331    use super::*;
332    use crate::{
333        Ed25519PublicKey, Secp256k1PublicKey, Secp256r1PublicKey, SignatureScheme,
334        crypto::SignatureFromBytesError,
335    };
336
337    #[derive(serde::Deserialize)]
338    pub struct Multisig {
339        signatures: Vec<MultisigMemberSignature>,
340        bitmap: BitmapUnit,
341        committee: MultisigCommittee,
342    }
343
344    #[derive(serde::Serialize)]
345    pub struct MultisigRef<'a> {
346        signatures: &'a [MultisigMemberSignature],
347        bitmap: BitmapUnit,
348        committee: &'a MultisigCommittee,
349    }
350
351    #[derive(serde::Deserialize)]
352    struct ReadableMultisigAggregatedSignature {
353        signatures: Vec<MultisigMemberSignature>,
354        bitmap: BitmapUnit,
355        committee: MultisigCommittee,
356    }
357
358    #[derive(serde::Serialize)]
359    struct ReadableMultisigAggregatedSignatureRef<'a> {
360        signatures: &'a [MultisigMemberSignature],
361        bitmap: BitmapUnit,
362        committee: &'a MultisigCommittee,
363    }
364
365    impl Serialize for MultisigAggregatedSignature {
366        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
367        where
368            S: Serializer,
369        {
370            if serializer.is_human_readable() {
371                let readable = ReadableMultisigAggregatedSignatureRef {
372                    signatures: &self.signatures,
373                    bitmap: self.bitmap,
374                    committee: &self.committee,
375                };
376                readable.serialize(serializer)
377            } else {
378                let bytes = self.to_bytes();
379                serializer.serialize_bytes(&bytes)
380            }
381        }
382    }
383
384    impl<'de> Deserialize<'de> for MultisigAggregatedSignature {
385        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
386        where
387            D: Deserializer<'de>,
388        {
389            if deserializer.is_human_readable() {
390                let readable = ReadableMultisigAggregatedSignature::deserialize(deserializer)?;
391                Ok(Self {
392                    signatures: readable.signatures,
393                    bitmap: readable.bitmap,
394                    committee: readable.committee,
395                })
396            } else {
397                let bytes: Cow<'de, [u8]> = Bytes::deserialize_as(deserializer)?;
398                Self::from_serialized_bytes(bytes).map_err(serde::de::Error::custom)
399            }
400        }
401    }
402
403    impl MultisigAggregatedSignature {
404        pub(crate) fn to_bytes(&self) -> Vec<u8> {
405            let mut buf = Vec::new();
406            buf.push(SignatureScheme::Multisig as u8);
407
408            let multisig = MultisigRef {
409                signatures: &self.signatures,
410                bitmap: self.bitmap,
411                committee: &self.committee,
412            };
413            bcs::serialize_into(&mut buf, &multisig).expect("serialization cannot fail");
414            buf
415        }
416
417        pub fn from_serialized_bytes(
418            bytes: impl AsRef<[u8]>,
419        ) -> Result<Self, SignatureFromBytesError> {
420            let bytes = bytes.as_ref();
421            let flag =
422                SignatureScheme::from_byte(*bytes.first().ok_or_else(|| {
423                    SignatureFromBytesError::new("missing signature scheme flag")
424                })?)
425                .map_err(SignatureFromBytesError::new)?;
426            if flag != SignatureScheme::Multisig {
427                return Err(SignatureFromBytesError::new("invalid multisig flag"));
428            }
429            let bcs_bytes = &bytes[1..];
430
431            if let Ok(multisig) = bcs::from_bytes::<Multisig>(bcs_bytes) {
432                Ok(Self {
433                    signatures: multisig.signatures,
434                    bitmap: multisig.bitmap,
435                    committee: multisig.committee,
436                })
437            } else {
438                Err(SignatureFromBytesError::new("invalid multisig"))
439            }
440        }
441    }
442
443    #[derive(serde::Serialize, serde::Deserialize)]
444    enum MemberPublicKey {
445        Ed25519(Ed25519PublicKey),
446        Secp256k1(Secp256k1PublicKey),
447        Secp256r1(Secp256r1PublicKey),
448        ZkLogin(ZkLoginPublicIdentifier),
449    }
450
451    #[derive(serde::Serialize, serde::Deserialize)]
452    #[serde(tag = "scheme", rename_all = "lowercase")]
453    #[serde(rename = "MultisigMemberPublicKey")]
454    #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
455    enum ReadableMemberPublicKey {
456        Ed25519 { public_key: Ed25519PublicKey },
457        Secp256k1 { public_key: Secp256k1PublicKey },
458        Secp256r1 { public_key: Secp256r1PublicKey },
459        ZkLogin(ZkLoginPublicIdentifier),
460    }
461
462    #[cfg(feature = "schemars")]
463    impl schemars::JsonSchema for MultisigMemberPublicKey {
464        fn schema_name() -> String {
465            ReadableMemberPublicKey::schema_name()
466        }
467
468        fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
469            ReadableMemberPublicKey::json_schema(gen)
470        }
471    }
472
473    impl Serialize for MultisigMemberPublicKey {
474        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
475        where
476            S: Serializer,
477        {
478            if serializer.is_human_readable() {
479                let readable = match self {
480                    MultisigMemberPublicKey::Ed25519(public_key) => {
481                        ReadableMemberPublicKey::Ed25519 {
482                            public_key: *public_key,
483                        }
484                    }
485                    MultisigMemberPublicKey::Secp256k1(public_key) => {
486                        ReadableMemberPublicKey::Secp256k1 {
487                            public_key: *public_key,
488                        }
489                    }
490                    MultisigMemberPublicKey::Secp256r1(public_key) => {
491                        ReadableMemberPublicKey::Secp256r1 {
492                            public_key: *public_key,
493                        }
494                    }
495                    MultisigMemberPublicKey::ZkLogin(public_id) => {
496                        ReadableMemberPublicKey::ZkLogin(public_id.clone())
497                    }
498                };
499                readable.serialize(serializer)
500            } else {
501                let binary = match self {
502                    MultisigMemberPublicKey::Ed25519(public_key) => {
503                        MemberPublicKey::Ed25519(*public_key)
504                    }
505                    MultisigMemberPublicKey::Secp256k1(public_key) => {
506                        MemberPublicKey::Secp256k1(*public_key)
507                    }
508                    MultisigMemberPublicKey::Secp256r1(public_key) => {
509                        MemberPublicKey::Secp256r1(*public_key)
510                    }
511                    MultisigMemberPublicKey::ZkLogin(public_id) => {
512                        MemberPublicKey::ZkLogin(public_id.clone())
513                    }
514                };
515                binary.serialize(serializer)
516            }
517        }
518    }
519
520    impl<'de> Deserialize<'de> for MultisigMemberPublicKey {
521        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
522        where
523            D: Deserializer<'de>,
524        {
525            if deserializer.is_human_readable() {
526                let readable = ReadableMemberPublicKey::deserialize(deserializer)?;
527                Ok(match readable {
528                    ReadableMemberPublicKey::Ed25519 { public_key } => Self::Ed25519(public_key),
529                    ReadableMemberPublicKey::Secp256k1 { public_key } => {
530                        Self::Secp256k1(public_key)
531                    }
532                    ReadableMemberPublicKey::Secp256r1 { public_key } => {
533                        Self::Secp256r1(public_key)
534                    }
535                    ReadableMemberPublicKey::ZkLogin(public_id) => Self::ZkLogin(public_id),
536                })
537            } else {
538                let binary = MemberPublicKey::deserialize(deserializer)?;
539                Ok(match binary {
540                    MemberPublicKey::Ed25519(public_key) => Self::Ed25519(public_key),
541                    MemberPublicKey::Secp256k1(public_key) => Self::Secp256k1(public_key),
542                    MemberPublicKey::Secp256r1(public_key) => Self::Secp256r1(public_key),
543                    MemberPublicKey::ZkLogin(public_id) => Self::ZkLogin(public_id),
544                })
545            }
546        }
547    }
548
549    #[derive(serde::Serialize, serde::Deserialize)]
550    enum MemberSignature {
551        Ed25519(Ed25519Signature),
552        Secp256k1(Secp256k1Signature),
553        Secp256r1(Secp256r1Signature),
554        ZkLogin(Box<ZkLoginAuthenticator>),
555    }
556
557    #[derive(serde::Serialize, serde::Deserialize)]
558    #[serde(tag = "scheme", rename_all = "lowercase")]
559    #[serde(rename = "MultisigMemberSignature")]
560    #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
561    enum ReadableMemberSignature {
562        Ed25519 { signature: Ed25519Signature },
563        Secp256k1 { signature: Secp256k1Signature },
564        Secp256r1 { signature: Secp256r1Signature },
565        ZkLogin(Box<ZkLoginAuthenticator>),
566    }
567
568    #[cfg(feature = "schemars")]
569    impl schemars::JsonSchema for MultisigMemberSignature {
570        fn schema_name() -> String {
571            ReadableMemberSignature::schema_name()
572        }
573
574        fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
575            ReadableMemberSignature::json_schema(gen)
576        }
577    }
578
579    impl Serialize for MultisigMemberSignature {
580        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
581        where
582            S: Serializer,
583        {
584            if serializer.is_human_readable() {
585                let readable = match self {
586                    MultisigMemberSignature::Ed25519(signature) => {
587                        ReadableMemberSignature::Ed25519 {
588                            signature: *signature,
589                        }
590                    }
591                    MultisigMemberSignature::Secp256k1(signature) => {
592                        ReadableMemberSignature::Secp256k1 {
593                            signature: *signature,
594                        }
595                    }
596                    MultisigMemberSignature::Secp256r1(signature) => {
597                        ReadableMemberSignature::Secp256r1 {
598                            signature: *signature,
599                        }
600                    }
601                    MultisigMemberSignature::ZkLogin(authenticator) => {
602                        ReadableMemberSignature::ZkLogin(authenticator.clone())
603                    }
604                };
605                readable.serialize(serializer)
606            } else {
607                let binary = match self {
608                    MultisigMemberSignature::Ed25519(signature) => {
609                        MemberSignature::Ed25519(*signature)
610                    }
611                    MultisigMemberSignature::Secp256k1(signature) => {
612                        MemberSignature::Secp256k1(*signature)
613                    }
614                    MultisigMemberSignature::Secp256r1(signature) => {
615                        MemberSignature::Secp256r1(*signature)
616                    }
617                    MultisigMemberSignature::ZkLogin(authenticator) => {
618                        MemberSignature::ZkLogin(authenticator.clone())
619                    }
620                };
621                binary.serialize(serializer)
622            }
623        }
624    }
625
626    impl<'de> Deserialize<'de> for MultisigMemberSignature {
627        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
628        where
629            D: Deserializer<'de>,
630        {
631            if deserializer.is_human_readable() {
632                let readable = ReadableMemberSignature::deserialize(deserializer)?;
633                Ok(match readable {
634                    ReadableMemberSignature::Ed25519 { signature } => Self::Ed25519(signature),
635                    ReadableMemberSignature::Secp256k1 { signature } => Self::Secp256k1(signature),
636                    ReadableMemberSignature::Secp256r1 { signature } => Self::Secp256r1(signature),
637                    ReadableMemberSignature::ZkLogin(authenticator) => Self::ZkLogin(authenticator),
638                })
639            } else {
640                let binary = MemberSignature::deserialize(deserializer)?;
641                Ok(match binary {
642                    MemberSignature::Ed25519(signature) => Self::Ed25519(signature),
643                    MemberSignature::Secp256k1(signature) => Self::Secp256k1(signature),
644                    MemberSignature::Secp256r1(signature) => Self::Secp256r1(signature),
645                    MemberSignature::ZkLogin(authenticator) => Self::ZkLogin(authenticator),
646                })
647            }
648        }
649    }
650}