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