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