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