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