sui_crypto/
multisig.rs

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