Skip to main content

sui_types/
signature.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::committee::EpochId;
5use crate::crypto::{
6    CompressedSignature, PasskeyAuthenticatorAsBytes, PublicKey, SignatureScheme, SuiSignature,
7    ZkLoginAuthenticatorAsBytes,
8};
9use crate::digests::ZKLoginInputsDigest;
10use crate::error::{SuiError, SuiErrorKind};
11use crate::multisig_legacy::MultiSigLegacy;
12use crate::passkey_authenticator::PasskeyAuthenticator;
13use crate::signature_verification::VerifiedDigestCache;
14use crate::zk_login_authenticator::ZkLoginAuthenticator;
15use crate::{base_types::SuiAddress, crypto::Signature, error::SuiResult, multisig::MultiSig};
16pub use enum_dispatch::enum_dispatch;
17use fastcrypto::ed25519::{Ed25519PublicKey, Ed25519Signature};
18use fastcrypto::secp256k1::{Secp256k1PublicKey, Secp256k1Signature};
19use fastcrypto::secp256r1::{Secp256r1PublicKey, Secp256r1Signature};
20use fastcrypto::{
21    error::FastCryptoError,
22    traits::{EncodeDecodeBase64, ToFromBytes},
23};
24use fastcrypto_pq::mldsa65::{MLDSA65PublicKey, MLDSA65Signature};
25use fastcrypto_zkp::bn254::zk_login::{JWK, JwkId, OIDCProvider};
26use fastcrypto_zkp::bn254::zk_login_api::ZkLoginEnv;
27use imbl::hashmap::HashMap as ImHashMap;
28use schemars::JsonSchema;
29use serde::Serialize;
30use shared_crypto::intent::IntentMessage;
31use std::hash::Hash;
32use std::sync::Arc;
33#[derive(Default, Debug, Clone)]
34pub struct VerifyParams {
35    // map from JwkId (iss, kid) => JWK
36    pub oidc_provider_jwks: ImHashMap<JwkId, JWK>,
37    pub supported_providers: Vec<OIDCProvider>,
38    pub zk_login_env: ZkLoginEnv,
39    // zkLogin circuit verify mode: 0 = v1 circuit only, 1 = v2 circuit with
40    // fallback to v1, 2 = v2 circuit only.
41    pub zklogin_circuit_mode: u64,
42    pub verify_legacy_zklogin_address: bool,
43    pub accept_zklogin_in_multisig: bool,
44    pub accept_passkey_in_multisig: bool,
45    pub accept_mldsa65_in_multisig: bool,
46    pub zklogin_max_epoch_upper_bound_delta: Option<u64>,
47    pub additional_multisig_checks: bool,
48    pub validate_zklogin_public_identifier: bool,
49}
50
51impl VerifyParams {
52    pub fn new(
53        oidc_provider_jwks: ImHashMap<JwkId, JWK>,
54        supported_providers: Vec<OIDCProvider>,
55        zk_login_env: ZkLoginEnv,
56        zklogin_circuit_mode: u64,
57        verify_legacy_zklogin_address: bool,
58        accept_zklogin_in_multisig: bool,
59        accept_passkey_in_multisig: bool,
60        zklogin_max_epoch_upper_bound_delta: Option<u64>,
61        additional_multisig_checks: bool,
62        validate_zklogin_public_identifier: bool,
63    ) -> Self {
64        Self {
65            oidc_provider_jwks,
66            supported_providers,
67            zk_login_env,
68            zklogin_circuit_mode,
69            verify_legacy_zklogin_address,
70            accept_zklogin_in_multisig,
71            accept_passkey_in_multisig,
72            accept_mldsa65_in_multisig: false,
73            zklogin_max_epoch_upper_bound_delta,
74            additional_multisig_checks,
75            validate_zklogin_public_identifier,
76        }
77    }
78
79    /// Whether an ML-DSA-65 member's signature inside a multisig is accepted.
80    /// Off by default: only the validator path turns it on, from the
81    /// `mldsa65_auth` protocol flag, so client-side verifiers match validators
82    /// until the scheme is enabled.
83    pub fn with_mldsa65_in_multisig(mut self, accept: bool) -> Self {
84        self.accept_mldsa65_in_multisig = accept;
85        self
86    }
87}
88
89/// A lightweight trait that all members of [enum GenericSignature] implement.
90#[enum_dispatch]
91pub trait AuthenticatorTrait {
92    fn verify_user_authenticator_epoch(
93        &self,
94        epoch: EpochId,
95        max_epoch_upper_bound_delta: Option<u64>,
96    ) -> SuiResult;
97
98    fn verify_claims<T>(
99        &self,
100        value: &IntentMessage<T>,
101        author: SuiAddress,
102        aux_verify_data: &VerifyParams,
103        zklogin_inputs_cache: Arc<VerifiedDigestCache<ZKLoginInputsDigest>>,
104    ) -> SuiResult
105    where
106        T: Serialize;
107}
108
109/// Due to the incompatibility of [enum Signature] (which dispatches a trait that
110/// assumes signature and pubkey bytes for verification), here we add a wrapper
111/// enum where member can just implement a lightweight [trait AuthenticatorTrait].
112/// This way MultiSig (and future Authenticators) can implement its own `verify`.
113#[enum_dispatch(AuthenticatorTrait)]
114#[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Hash)]
115pub enum GenericSignature {
116    MultiSig,
117    MultiSigLegacy,
118    Signature,
119    ZkLoginAuthenticator,
120    PasskeyAuthenticator,
121}
122
123impl GenericSignature {
124    pub fn is_zklogin(&self) -> bool {
125        matches!(self, GenericSignature::ZkLoginAuthenticator(_))
126    }
127    pub fn is_passkey(&self) -> bool {
128        matches!(self, GenericSignature::PasskeyAuthenticator(_))
129    }
130
131    pub fn is_mldsa65(&self) -> bool {
132        matches!(self, GenericSignature::Signature(s) if s.scheme() == SignatureScheme::MLDSA65)
133    }
134
135    pub fn is_upgraded_multisig(&self) -> bool {
136        matches!(self, GenericSignature::MultiSig(_))
137    }
138
139    pub fn verify_authenticator<T>(
140        &self,
141        value: &IntentMessage<T>,
142        author: SuiAddress,
143        epoch: EpochId,
144        verify_params: &VerifyParams,
145        zklogin_inputs_cache: Arc<VerifiedDigestCache<ZKLoginInputsDigest>>,
146    ) -> SuiResult
147    where
148        T: Serialize,
149    {
150        self.verify_user_authenticator_epoch(
151            epoch,
152            verify_params.zklogin_max_epoch_upper_bound_delta,
153        )?;
154        self.verify_claims(value, author, verify_params, zklogin_inputs_cache)
155    }
156
157    /// Parse [enum CompressedSignature] from trait SuiSignature `flag || sig || pk`.
158    /// This is useful for the MultiSig to combine partial signature into a MultiSig public key.
159    pub fn to_compressed(&self) -> Result<CompressedSignature, SuiError> {
160        match self {
161            GenericSignature::Signature(s) => {
162                let bytes = s.signature_bytes();
163                match s.scheme() {
164                    SignatureScheme::ED25519 => Ok(CompressedSignature::Ed25519(
165                        (&Ed25519Signature::from_bytes(bytes).map_err(|_| {
166                            SuiErrorKind::InvalidSignature {
167                                error: "Cannot parse ed25519 sig".to_string(),
168                            }
169                        })?)
170                            .into(),
171                    )),
172                    SignatureScheme::Secp256k1 => Ok(CompressedSignature::Secp256k1(
173                        (&Secp256k1Signature::from_bytes(bytes).map_err(|_| {
174                            SuiErrorKind::InvalidSignature {
175                                error: "Cannot parse secp256k1 sig".to_string(),
176                            }
177                        })?)
178                            .into(),
179                    )),
180                    SignatureScheme::MLDSA65 => Ok(CompressedSignature::MLDSA65(Box::new(
181                        (&MLDSA65Signature::from_bytes(bytes).map_err(|_| {
182                            SuiErrorKind::InvalidSignature {
183                                error: "Cannot parse mldsa65 sig".to_string(),
184                            }
185                        })?)
186                            .into(),
187                    ))),
188                    SignatureScheme::Secp256r1 | SignatureScheme::PasskeyAuthenticator => {
189                        Ok(CompressedSignature::Secp256r1(
190                            (&Secp256r1Signature::from_bytes(bytes).map_err(|_| {
191                                SuiErrorKind::InvalidSignature {
192                                    error: "Cannot parse secp256r1 sig".to_string(),
193                                }
194                            })?)
195                                .into(),
196                        ))
197                    }
198
199                    _ => Err(SuiErrorKind::UnsupportedFeatureError {
200                        error: "Unsupported signature scheme".to_string(),
201                    }
202                    .into()),
203                }
204            }
205            GenericSignature::ZkLoginAuthenticator(s) => Ok(CompressedSignature::ZkLogin(
206                ZkLoginAuthenticatorAsBytes(s.as_ref().to_vec()),
207            )),
208            GenericSignature::PasskeyAuthenticator(s) => Ok(CompressedSignature::Passkey(
209                PasskeyAuthenticatorAsBytes(s.as_ref().to_vec()),
210            )),
211            _ => Err(SuiErrorKind::UnsupportedFeatureError {
212                error: "Unsupported signature scheme".to_string(),
213            }
214            .into()),
215        }
216    }
217
218    /// Parse [struct PublicKey] from trait SuiSignature `flag || sig || pk`.
219    /// This is useful for the MultiSig to construct the bitmap in [struct MultiPublicKey].
220    pub fn to_public_key(&self) -> Result<PublicKey, SuiError> {
221        match self {
222            GenericSignature::Signature(s) => {
223                let bytes = s.public_key_bytes();
224                match s.scheme() {
225                    SignatureScheme::ED25519 => Ok(PublicKey::Ed25519(
226                        (&Ed25519PublicKey::from_bytes(bytes).map_err(|_| {
227                            SuiErrorKind::KeyConversionError("Cannot parse ed25519 pk".to_string())
228                        })?)
229                            .into(),
230                    )),
231                    SignatureScheme::Secp256k1 => Ok(PublicKey::Secp256k1(
232                        (&Secp256k1PublicKey::from_bytes(bytes).map_err(|_| {
233                            SuiErrorKind::KeyConversionError(
234                                "Cannot parse secp256k1 pk".to_string(),
235                            )
236                        })?)
237                            .into(),
238                    )),
239                    SignatureScheme::MLDSA65 => Ok(PublicKey::MLDSA65(Box::new(
240                        (&MLDSA65PublicKey::from_bytes(bytes).map_err(|_| {
241                            SuiErrorKind::KeyConversionError("Cannot parse mldsa65 pk".to_string())
242                        })?)
243                            .into(),
244                    ))),
245                    SignatureScheme::Secp256r1 => Ok(PublicKey::Secp256r1(
246                        (&Secp256r1PublicKey::from_bytes(bytes).map_err(|_| {
247                            SuiErrorKind::KeyConversionError(
248                                "Cannot parse secp256r1 pk".to_string(),
249                            )
250                        })?)
251                            .into(),
252                    )),
253                    _ => Err(SuiErrorKind::UnsupportedFeatureError {
254                        error: "Unsupported signature scheme in MultiSig".to_string(),
255                    }
256                    .into()),
257                }
258            }
259            GenericSignature::ZkLoginAuthenticator(s) => s.get_pk(),
260            GenericSignature::PasskeyAuthenticator(s) => s.get_pk(),
261            _ => Err(SuiErrorKind::UnsupportedFeatureError {
262                error: "Unsupported signature scheme".to_string(),
263            }
264            .into()),
265        }
266    }
267}
268
269/// GenericSignature encodes a single signature [enum Signature] as is `flag || signature || pubkey`.
270/// It encodes [struct MultiSigLegacy] as the MultiSig flag (0x03) concat with the bcs serializedbytes
271/// of [struct MultiSigLegacy] i.e. `flag || bcs_bytes(MultiSigLegacy)`.
272/// [struct Multisig] is encodede as the MultiSig flag (0x03) concat with the bcs serializedbytes
273/// of [struct Multisig] i.e. `flag || bcs_bytes(Multisig)`.
274impl ToFromBytes for GenericSignature {
275    fn from_bytes(bytes: &[u8]) -> Result<Self, FastCryptoError> {
276        match SignatureScheme::from_flag_byte(
277            bytes.first().ok_or(FastCryptoError::InputTooShort(0))?,
278        ) {
279            Ok(x) => match x {
280                SignatureScheme::ED25519
281                | SignatureScheme::Secp256k1
282                | SignatureScheme::Secp256r1
283                | SignatureScheme::MLDSA65 => Ok(GenericSignature::Signature(
284                    Signature::from_bytes(bytes).map_err(|_| FastCryptoError::InvalidSignature)?,
285                )),
286                SignatureScheme::MultiSig => match MultiSig::from_bytes(bytes) {
287                    Ok(multisig) => Ok(GenericSignature::MultiSig(multisig)),
288                    Err(_) => {
289                        let multisig = MultiSigLegacy::from_bytes(bytes)?;
290                        Ok(GenericSignature::MultiSigLegacy(multisig))
291                    }
292                },
293                SignatureScheme::ZkLoginAuthenticator => {
294                    let zk_login = ZkLoginAuthenticator::from_bytes(bytes)?;
295                    Ok(GenericSignature::ZkLoginAuthenticator(zk_login))
296                }
297                SignatureScheme::PasskeyAuthenticator => {
298                    let passkey = PasskeyAuthenticator::from_bytes(bytes)?;
299                    Ok(GenericSignature::PasskeyAuthenticator(passkey))
300                }
301                _ => Err(FastCryptoError::InvalidInput),
302            },
303            Err(_) => Err(FastCryptoError::InvalidInput),
304        }
305    }
306}
307
308/// Trait useful to get the bytes reference for [enum GenericSignature].
309impl AsRef<[u8]> for GenericSignature {
310    fn as_ref(&self) -> &[u8] {
311        match self {
312            GenericSignature::MultiSig(s) => s.as_ref(),
313            GenericSignature::MultiSigLegacy(s) => s.as_ref(),
314            GenericSignature::Signature(s) => s.as_ref(),
315            GenericSignature::ZkLoginAuthenticator(s) => s.as_ref(),
316            GenericSignature::PasskeyAuthenticator(s) => s.as_ref(),
317        }
318    }
319}
320
321impl ::serde::Serialize for GenericSignature {
322    fn serialize<S: ::serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
323        if serializer.is_human_readable() {
324            #[derive(serde::Serialize)]
325            struct GenericSignature(String);
326            GenericSignature(self.encode_base64()).serialize(serializer)
327        } else {
328            #[derive(serde::Serialize)]
329            struct GenericSignature<'a>(&'a [u8]);
330            GenericSignature(self.as_ref()).serialize(serializer)
331        }
332    }
333}
334
335impl<'de> ::serde::Deserialize<'de> for GenericSignature {
336    fn deserialize<D: ::serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
337        use serde::de::Error;
338
339        if deserializer.is_human_readable() {
340            #[derive(serde::Deserialize)]
341            struct GenericSignature(String);
342            let s = GenericSignature::deserialize(deserializer)?;
343            Self::decode_base64(&s.0).map_err(::serde::de::Error::custom)
344        } else {
345            #[derive(serde::Deserialize)]
346            struct GenericSignature(Vec<u8>);
347
348            let data = GenericSignature::deserialize(deserializer)?;
349            Self::from_bytes(&data.0).map_err(|e| Error::custom(e.to_string()))
350        }
351    }
352}
353
354/// This ports the wrapper trait to the verify_secure defined on [enum Signature].
355impl AuthenticatorTrait for Signature {
356    fn verify_user_authenticator_epoch(&self, _: EpochId, _: Option<EpochId>) -> SuiResult {
357        Ok(())
358    }
359
360    fn verify_claims<T>(
361        &self,
362        value: &IntentMessage<T>,
363        author: SuiAddress,
364        _aux_verify_data: &VerifyParams,
365        _zklogin_inputs_cache: Arc<VerifiedDigestCache<ZKLoginInputsDigest>>,
366    ) -> SuiResult
367    where
368        T: Serialize,
369    {
370        self.verify_secure(value, author, self.scheme())
371    }
372}