Skip to main content

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