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