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