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