iota_sdk_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 iota_types::{
6    MultisigAggregatedSignature, MultisigCommittee, MultisigMemberPublicKey,
7    MultisigMemberSignature, UserSignature,
8};
9
10use crate::{SignatureError, Verifier};
11
12#[derive(Default, Debug, Clone, PartialEq)]
13pub struct MultisigVerifier {
14    #[cfg(feature = "zklogin")]
15    zklogin_verifier: Option<crate::zklogin::ZkloginVerifier>,
16}
17
18impl MultisigVerifier {
19    pub fn new() -> Self {
20        Default::default()
21    }
22
23    fn verify_member_signature(
24        &self,
25        message: &[u8],
26        member_public_key: &MultisigMemberPublicKey,
27        signature: &MultisigMemberSignature,
28    ) -> Result<(), SignatureError> {
29        match (member_public_key, signature) {
30            #[cfg(not(feature = "ed25519"))]
31            (MultisigMemberPublicKey::Ed25519(_), MultisigMemberSignature::Ed25519(_)) => Err(
32                SignatureError::from_source("support for ed25519 is not enabled"),
33            ),
34            #[cfg(feature = "ed25519")]
35            (
36                MultisigMemberPublicKey::Ed25519(ed25519_public_key),
37                MultisigMemberSignature::Ed25519(ed25519_signature),
38            ) => crate::ed25519::Ed25519VerifyingKey::new(ed25519_public_key)?
39                .verify(message, ed25519_signature),
40            #[cfg(not(feature = "secp256k1"))]
41            (MultisigMemberPublicKey::Secp256k1(_), MultisigMemberSignature::Secp256k1(_)) => Err(
42                SignatureError::from_source("support for secp256k1 is not enabled"),
43            ),
44            #[cfg(feature = "secp256k1")]
45            (
46                MultisigMemberPublicKey::Secp256k1(k1_public_key),
47                MultisigMemberSignature::Secp256k1(k1_signature),
48            ) => crate::secp256k1::Secp256k1VerifyingKey::new(k1_public_key)?
49                .verify(message, k1_signature),
50            #[cfg(not(feature = "secp256r1"))]
51            (MultisigMemberPublicKey::Secp256r1(_), MultisigMemberSignature::Secp256r1(_)) => Err(
52                SignatureError::from_source("support for secp256r1 is not enabled"),
53            ),
54            #[cfg(feature = "secp256r1")]
55            (
56                MultisigMemberPublicKey::Secp256r1(r1_public_key),
57                MultisigMemberSignature::Secp256r1(r1_signature),
58            ) => crate::secp256r1::Secp256r1VerifyingKey::new(r1_public_key)?
59                .verify(message, r1_signature),
60            #[cfg(not(feature = "zklogin"))]
61            (MultisigMemberPublicKey::ZkLogin(_), MultisigMemberSignature::ZkLogin(_)) => Err(
62                SignatureError::from_source("support for zklogin is not enabled"),
63            ),
64            #[cfg(feature = "zklogin")]
65            (
66                MultisigMemberPublicKey::ZkLogin(zklogin_identifier),
67                MultisigMemberSignature::ZkLogin(zklogin_authenticator),
68            ) => {
69                let zklogin_verifier = self
70                    .zklogin_verifier()
71                    .ok_or_else(|| SignatureError::from_source("no zklogin verifier provided"))?;
72
73                // verify that the member identifier and the authenticator match
74                if zklogin_identifier != zklogin_authenticator.inputs.public_identifier() {
75                    return Err(SignatureError::from_source(
76                        "member zklogin identifier does not match signature",
77                    ));
78                }
79
80                zklogin_verifier.verify(message, zklogin_authenticator.as_ref())
81            }
82
83            _ => Err(SignatureError::from_source(
84                "member and signature scheme do not match",
85            )),
86        }
87    }
88}
89
90#[cfg(feature = "zklogin")]
91#[cfg_attr(doc_cfg, doc(cfg(feature = "zklogin")))]
92impl MultisigVerifier {
93    pub fn with_zklogin_verifier(&mut self, zklogin_verifier: crate::zklogin::ZkloginVerifier) {
94        self.zklogin_verifier = Some(zklogin_verifier);
95    }
96
97    pub fn zklogin_verifier(&self) -> Option<&crate::zklogin::ZkloginVerifier> {
98        self.zklogin_verifier.as_ref()
99    }
100
101    pub fn zklogin_verifier_mut(&mut self) -> Option<&mut crate::zklogin::ZkloginVerifier> {
102        self.zklogin_verifier.as_mut()
103    }
104}
105
106impl Verifier<MultisigAggregatedSignature> for MultisigVerifier {
107    fn verify(
108        &self,
109        message: &[u8],
110        signature: &MultisigAggregatedSignature,
111    ) -> Result<(), SignatureError> {
112        if !signature.committee().is_valid() {
113            return Err(SignatureError::from_source("invalid MultisigCommittee"));
114        }
115
116        if signature.signatures().len() != signature.bitmap().count_ones() as usize {
117            return Err(SignatureError::from_source(
118                "number of signatures does not match bitmap",
119            ));
120        }
121
122        if signature.signatures().len() > signature.committee().members().len() {
123            return Err(SignatureError::from_source(
124                "more signatures than committee members",
125            ));
126        }
127
128        let weight = BitmapIndices::new(signature.bitmap())
129            .map(|member_idx| {
130                signature
131                    .committee()
132                    .members()
133                    .get(member_idx as usize)
134                    .ok_or_else(|| SignatureError::from_source("invalid bitmap"))
135            })
136            .zip(signature.signatures())
137            .map(|(maybe_member, signature)| {
138                let member = maybe_member?;
139                self.verify_member_signature(message, member.public_key(), signature)
140                    .map(|()| member.weight() as u16)
141            })
142            .sum::<Result<u16, SignatureError>>()?;
143
144        if weight >= signature.committee().threshold() {
145            Ok(())
146        } else {
147            Err(SignatureError::from_source(
148                "signature weight does not exceed threshold",
149            ))
150        }
151    }
152}
153
154impl Verifier<UserSignature> for MultisigVerifier {
155    fn verify(&self, message: &[u8], signature: &UserSignature) -> Result<(), SignatureError> {
156        let UserSignature::Multisig(signature) = signature else {
157            return Err(SignatureError::from_source("not a multisig signature"));
158        };
159
160        self.verify(message, signature)
161    }
162}
163
164/// Interpret a bitmap of 01s as a list of indices that is set to 1s.
165/// e.g. 22 = 0b10110, then the result is [1, 2, 4].
166struct BitmapIndices {
167    bitmap: u16,
168    range: std::ops::Range<u8>,
169}
170
171impl BitmapIndices {
172    pub fn new(bitmap: u16) -> Self {
173        Self {
174            bitmap,
175            range: 0..(u16::BITS as u8),
176        }
177    }
178}
179
180impl Iterator for BitmapIndices {
181    type Item = u8;
182
183    fn next(&mut self) -> Option<Self::Item> {
184        #[allow(clippy::while_let_on_iterator)]
185        while let Some(i) = self.range.next() {
186            if self.bitmap & (1 << i) != 0 {
187                return Some(i);
188            }
189        }
190
191        None
192    }
193}
194
195/// Verifier that will verify all UserSignature variants
196#[derive(Default, Debug, Clone, PartialEq)]
197pub struct UserSignatureVerifier {
198    inner: MultisigVerifier,
199}
200
201impl UserSignatureVerifier {
202    pub fn new() -> Self {
203        Default::default()
204    }
205}
206
207#[cfg(feature = "zklogin")]
208#[cfg_attr(doc_cfg, doc(cfg(feature = "zklogin")))]
209impl UserSignatureVerifier {
210    pub fn with_zklogin_verifier(&mut self, zklogin_verifier: crate::zklogin::ZkloginVerifier) {
211        self.inner.with_zklogin_verifier(zklogin_verifier);
212    }
213
214    pub fn zklogin_verifier(&self) -> Option<&crate::zklogin::ZkloginVerifier> {
215        self.inner.zklogin_verifier()
216    }
217
218    pub fn zklogin_verifier_mut(&mut self) -> Option<&mut crate::zklogin::ZkloginVerifier> {
219        self.inner.zklogin_verifier_mut()
220    }
221}
222
223impl Verifier<UserSignature> for UserSignatureVerifier {
224    fn verify(&self, message: &[u8], signature: &UserSignature) -> Result<(), SignatureError> {
225        match signature {
226            UserSignature::Simple(simple_signature) => {
227                crate::simple::SimpleVerifier.verify(message, simple_signature)
228            }
229            UserSignature::Multisig(multisig) => self.inner.verify(message, multisig),
230            #[cfg(not(feature = "zklogin"))]
231            UserSignature::ZkLogin(_) => Err(SignatureError::from_source(
232                "support for zklogin is not enabled",
233            )),
234            #[cfg(feature = "zklogin")]
235            UserSignature::ZkLogin(zklogin_authenticator) => {
236                let zklogin_verifier = self
237                    .zklogin_verifier()
238                    .ok_or_else(|| SignatureError::from_source("no zklogin verifier provided"))?;
239
240                zklogin_verifier.verify(message, zklogin_authenticator.as_ref())
241            }
242            #[cfg(not(feature = "passkey"))]
243            UserSignature::Passkey(_) => Err(SignatureError::from_source(
244                "support for passkey is not enabled",
245            )),
246            #[cfg(feature = "passkey")]
247            UserSignature::Passkey(passkey_authenticator) => {
248                crate::passkey::PasskeyVerifier::default().verify(message, passkey_authenticator)
249            }
250        }
251    }
252}
253
254#[derive(Debug, Clone, PartialEq)]
255pub struct MultisigAggregator {
256    committee: MultisigCommittee,
257    signatures: std::collections::BTreeMap<usize, MultisigMemberSignature>,
258    signed_weight: u16,
259    message: Vec<u8>,
260    verifier: MultisigVerifier,
261}
262
263impl MultisigAggregator {
264    pub fn new_with_transaction(
265        committee: MultisigCommittee,
266        transaction: &iota_types::Transaction,
267    ) -> Self {
268        Self {
269            committee,
270            signatures: Default::default(),
271            signed_weight: 0,
272            message: transaction.signing_digest().to_vec(),
273            verifier: Default::default(),
274        }
275    }
276
277    pub fn new_with_message(
278        committee: MultisigCommittee,
279        message: &iota_types::PersonalMessage<'_>,
280    ) -> Self {
281        Self {
282            committee,
283            signatures: Default::default(),
284            signed_weight: 0,
285            message: message.signing_digest().to_vec(),
286            verifier: Default::default(),
287        }
288    }
289
290    pub fn verifier(&self) -> &MultisigVerifier {
291        &self.verifier
292    }
293
294    pub fn verifier_mut(&mut self) -> &mut MultisigVerifier {
295        &mut self.verifier
296    }
297
298    pub fn add_signature(&mut self, signature: UserSignature) -> Result<(), SignatureError> {
299        use std::collections::btree_map::Entry;
300
301        let (public_key, signature) = multisig_pubkey_and_signature_from_user_signature(signature)?;
302        let member_idx = self
303            .committee
304            .members()
305            .iter()
306            .position(|member| member.public_key() == &public_key)
307            .ok_or_else(|| {
308                SignatureError::from_source(
309                    "provided signature does not belong to committee member",
310                )
311            })?;
312
313        self.verifier()
314            .verify_member_signature(&self.message, &public_key, &signature)?;
315
316        match self.signatures.entry(member_idx) {
317            Entry::Vacant(v) => {
318                v.insert(signature);
319            }
320            Entry::Occupied(_) => {
321                return Err(SignatureError::from_source(
322                    "duplicate signature from same committee member",
323                ));
324            }
325        }
326
327        self.signed_weight += self.committee.members()[member_idx].weight() as u16;
328
329        Ok(())
330    }
331
332    pub fn finish(&self) -> Result<MultisigAggregatedSignature, SignatureError> {
333        if self.signed_weight < self.committee.threshold() {
334            return Err(SignatureError::from_source(
335                "insufficient signature weight to reach threshold",
336            ));
337        }
338
339        let (signatures, bitmap) = self.signatures.clone().into_iter().fold(
340            (Vec::new(), 0),
341            |(mut signatures, mut bitmap), (member_idx, signature)| {
342                bitmap |= 1 << member_idx;
343                signatures.push(signature);
344                (signatures, bitmap)
345            },
346        );
347
348        Ok(MultisigAggregatedSignature::new(
349            self.committee.clone(),
350            signatures,
351            bitmap,
352        ))
353    }
354}
355
356fn multisig_pubkey_and_signature_from_user_signature(
357    signature: UserSignature,
358) -> Result<(MultisigMemberPublicKey, MultisigMemberSignature), SignatureError> {
359    use iota_types::SimpleSignature;
360    match signature {
361        UserSignature::Simple(SimpleSignature::Ed25519 {
362            signature,
363            public_key,
364        }) => Ok((
365            MultisigMemberPublicKey::Ed25519(public_key),
366            MultisigMemberSignature::Ed25519(signature),
367        )),
368        UserSignature::Simple(SimpleSignature::Secp256k1 {
369            signature,
370            public_key,
371        }) => Ok((
372            MultisigMemberPublicKey::Secp256k1(public_key),
373            MultisigMemberSignature::Secp256k1(signature),
374        )),
375        UserSignature::Simple(SimpleSignature::Secp256r1 {
376            signature,
377            public_key,
378        }) => Ok((
379            MultisigMemberPublicKey::Secp256r1(public_key),
380            MultisigMemberSignature::Secp256r1(signature),
381        )),
382        #[cfg(not(feature = "zklogin"))]
383        UserSignature::ZkLogin(_) => Err(SignatureError::from_source(
384            "support for zklogin is not enabled",
385        )),
386        #[cfg(feature = "zklogin")]
387        UserSignature::ZkLogin(zklogin_authenticator) => {
388            let zklogin_identifier = zklogin_authenticator.inputs.public_identifier().to_owned();
389            Ok((
390                MultisigMemberPublicKey::ZkLogin(zklogin_identifier),
391                MultisigMemberSignature::ZkLogin(zklogin_authenticator),
392            ))
393        }
394
395        UserSignature::Multisig(_) | UserSignature::Passkey(_) => {
396            Err(SignatureError::from_source("invalid signature scheme"))
397        }
398    }
399}