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