iota_types/
signature.rs

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