Skip to main content

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;
6
7pub use enum_dispatch::enum_dispatch;
8use fastcrypto::{
9    ed25519::{Ed25519PublicKey, Ed25519Signature},
10    encoding::{Base64, Encoding},
11    error::{FastCryptoError, FastCryptoResult},
12    secp256k1::{Secp256k1PublicKey, Secp256k1Signature},
13    secp256r1::{Secp256r1PublicKey, Secp256r1Signature},
14    traits::{EncodeDecodeBase64, ToFromBytes},
15};
16use iota_sdk_types::{Address, crypto::IntentMessage};
17use serde::Serialize;
18use tracing::instrument;
19
20use crate::{
21    crypto::{
22        CompressedSignature, IotaSignature, PasskeyAuthenticatorAsBytes, PublicKey, Signature,
23        SignatureScheme,
24    },
25    error::{IotaError, IotaResult},
26    move_authenticator::MoveAuthenticator,
27    multisig::MultiSig,
28    passkey_authenticator::PasskeyAuthenticator,
29};
30#[derive(Default, Debug, Clone)]
31pub struct VerifyParams {
32    pub accept_passkey_in_multisig: bool,
33    pub additional_multisig_checks: bool,
34}
35
36impl VerifyParams {
37    pub fn new(accept_passkey_in_multisig: bool, additional_multisig_checks: bool) -> Self {
38        Self {
39            accept_passkey_in_multisig,
40            additional_multisig_checks,
41        }
42    }
43}
44
45/// A lightweight trait that all members of [enum GenericSignature] implement.
46#[enum_dispatch]
47pub trait AuthenticatorTrait {
48    fn verify_claims<T>(
49        &self,
50        value: &IntentMessage<T>,
51        author: Address,
52        aux_verify_data: &VerifyParams,
53    ) -> IotaResult
54    where
55        T: Serialize;
56}
57
58/// Deprecated zkLogin authenticator — empty stub retained only so the
59/// [`GenericSignature::ZkLoginAuthenticatorDeprecated`] enum variant compiles.
60/// Instances are never constructed; deserialization rejects the flag byte.
61#[iota_proc_macros::allow_deprecated_for_derives]
62#[deprecated(note = "zkLogin is deprecated and was never enabled on IOTA")]
63#[derive(Debug, Clone, PartialEq, Eq, Hash)]
64pub struct ZkLoginAuthenticatorDeprecated;
65
66#[allow(deprecated)]
67impl AuthenticatorTrait for ZkLoginAuthenticatorDeprecated {
68    fn verify_claims<T>(
69        &self,
70        _value: &IntentMessage<T>,
71        _author: Address,
72        _aux_verify_data: &VerifyParams,
73    ) -> IotaResult
74    where
75        T: Serialize,
76    {
77        Err(IotaError::UnsupportedFeature {
78            error: "zkLogin is not supported".to_string(),
79        })
80    }
81}
82
83#[allow(deprecated)]
84impl AsRef<[u8]> for ZkLoginAuthenticatorDeprecated {
85    fn as_ref(&self) -> &[u8] {
86        &[]
87    }
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#[iota_proc_macros::allow_deprecated_for_derives]
96#[enum_dispatch(AuthenticatorTrait)]
97#[derive(Debug, Clone, PartialEq, Eq, Hash)]
98#[allow(clippy::large_enum_variant)]
99pub enum GenericSignature {
100    MultiSig,
101    Signature,
102    #[deprecated(note = "zkLogin is deprecated and was never enabled on IOTA")]
103    ZkLoginAuthenticatorDeprecated,
104    PasskeyAuthenticator,
105    MoveAuthenticator,
106}
107
108impl GenericSignature {
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: Address,
125        verify_params: &VerifyParams,
126    ) -> IotaResult
127    where
128        T: Serialize,
129    {
130        self.verify_claims(value, author, verify_params)
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            #[allow(deprecated)]
173            GenericSignature::ZkLoginAuthenticatorDeprecated(_) => {
174                Err(IotaError::UnsupportedFeature {
175                    error: "zkLogin is not supported".to_string(),
176                })
177            }
178            GenericSignature::PasskeyAuthenticator(s) => Ok(CompressedSignature::Passkey(
179                PasskeyAuthenticatorAsBytes(s.to_bytes()),
180            )),
181            _ => Err(IotaError::UnsupportedFeature {
182                error: "Unsupported signature scheme".to_string(),
183            }),
184        }
185    }
186
187    /// Parse [struct PublicKey] from trait IotaSignature `flag || sig || pk`.
188    /// This is useful for the MultiSig to construct the bitmap in [struct
189    /// MultiPublicKey].
190    pub fn to_public_key(&self) -> Result<PublicKey, IotaError> {
191        match self {
192            GenericSignature::Signature(s) => {
193                let bytes = s.public_key_bytes();
194                match s.scheme() {
195                    SignatureScheme::ED25519 => Ok(PublicKey::Ed25519(
196                        (&Ed25519PublicKey::from_bytes(bytes).map_err(|_| {
197                            IotaError::KeyConversion("Cannot parse ed25519 pk".to_string())
198                        })?)
199                            .into(),
200                    )),
201                    SignatureScheme::Secp256k1 => Ok(PublicKey::Secp256k1(
202                        (&Secp256k1PublicKey::from_bytes(bytes).map_err(|_| {
203                            IotaError::KeyConversion("Cannot parse secp256k1 pk".to_string())
204                        })?)
205                            .into(),
206                    )),
207                    SignatureScheme::Secp256r1 => Ok(PublicKey::Secp256r1(
208                        (&Secp256r1PublicKey::from_bytes(bytes).map_err(|_| {
209                            IotaError::KeyConversion("Cannot parse secp256r1 pk".to_string())
210                        })?)
211                            .into(),
212                    )),
213                    _ => Err(IotaError::UnsupportedFeature {
214                        error: "Unsupported signature scheme in MultiSig".to_string(),
215                    }),
216                }
217            }
218            #[allow(deprecated)]
219            GenericSignature::ZkLoginAuthenticatorDeprecated(_) => {
220                Err(IotaError::UnsupportedFeature {
221                    error: "zkLogin is not supported".to_string(),
222                })
223            }
224            GenericSignature::PasskeyAuthenticator(passkey) => {
225                let pk = Secp256r1PublicKey::from_bytes(passkey.public_key().inner().as_ref())
226                    .map_err(|e| {
227                        IotaError::KeyConversion(format!("Cannot parse secp256r1 pk: {e}"))
228                    })?;
229                Ok(PublicKey::Passkey((&pk).into()))
230            }
231            GenericSignature::MoveAuthenticator(_) => Err(IotaError::UnsupportedFeature {
232                error: "Unsupported in MoveAuthenticator".to_string(),
233            }),
234            _ => Err(IotaError::UnsupportedFeature {
235                error: "Unsupported signature scheme".to_string(),
236            }),
237        }
238    }
239
240    pub fn to_bytes(&self) -> Vec<u8> {
241        match self {
242            GenericSignature::MultiSig(s) => s.to_bytes(),
243            GenericSignature::Signature(s) => s.as_ref().to_vec(),
244            #[allow(deprecated)]
245            GenericSignature::ZkLoginAuthenticatorDeprecated(s) => s.as_ref().to_vec(),
246            GenericSignature::PasskeyAuthenticator(s) => s.to_bytes(),
247            GenericSignature::MoveAuthenticator(s) => s.to_bytes(),
248        }
249    }
250
251    pub fn from_bytes(bytes: &[u8]) -> Result<Self, FastCryptoError> {
252        match SignatureScheme::from_flag_byte(
253            bytes.first().ok_or(FastCryptoError::InputTooShort(0))?,
254        ) {
255            Ok(x) => match x {
256                SignatureScheme::ED25519
257                | SignatureScheme::Secp256k1
258                | SignatureScheme::Secp256r1 => Ok(GenericSignature::Signature(
259                    Signature::from_bytes(bytes).map_err(|_| FastCryptoError::InvalidSignature)?,
260                )),
261                SignatureScheme::MultiSig => Ok(GenericSignature::MultiSig(
262                    MultiSig::from_bytes(bytes).map_err(|_| FastCryptoError::InvalidSignature)?,
263                )),
264                #[allow(deprecated)]
265                SignatureScheme::ZkLoginAuthenticatorDeprecated => {
266                    // zkLogin is deprecated and was never enabled on IOTA — reject at
267                    // deserialization.
268                    Err(FastCryptoError::GeneralError(
269                        "zkLogin is not supported".to_string(),
270                    ))
271                }
272                SignatureScheme::PasskeyAuthenticator => {
273                    let passkey = PasskeyAuthenticator::from_bytes(bytes)
274                        .map_err(|e| FastCryptoError::GeneralError(e.to_string()))?;
275                    Ok(GenericSignature::PasskeyAuthenticator(passkey))
276                }
277                SignatureScheme::MoveAuthenticator => {
278                    let move_auth = MoveAuthenticator::from_bytes(bytes)
279                        .map_err(|e| FastCryptoError::GeneralError(e.to_string()))?;
280                    Ok(GenericSignature::MoveAuthenticator(move_auth))
281                }
282                _ => Err(FastCryptoError::InvalidInput),
283            },
284            Err(_) => Err(FastCryptoError::InvalidInput),
285        }
286    }
287}
288
289impl EncodeDecodeBase64 for GenericSignature {
290    fn encode_base64(&self) -> String {
291        Base64::encode(self.to_bytes())
292    }
293
294    fn decode_base64(value: &str) -> FastCryptoResult<Self> {
295        let bytes = Base64::decode(value)?;
296        Self::from_bytes(&bytes)
297    }
298}
299
300impl ::serde::Serialize for GenericSignature {
301    fn serialize<S: ::serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
302        if serializer.is_human_readable() {
303            #[derive(serde::Serialize)]
304            struct GenericSignature(String);
305            GenericSignature(self.encode_base64()).serialize(serializer)
306        } else {
307            #[derive(serde::Serialize)]
308            struct GenericSignature<'a>(&'a [u8]);
309            GenericSignature(self.to_bytes().as_ref()).serialize(serializer)
310        }
311    }
312}
313
314impl<'de> ::serde::Deserialize<'de> for GenericSignature {
315    fn deserialize<D: ::serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
316        use serde::de::Error;
317
318        if deserializer.is_human_readable() {
319            #[derive(serde::Deserialize)]
320            struct GenericSignature(String);
321            let s = GenericSignature::deserialize(deserializer)?;
322            Self::decode_base64(&s.0).map_err(::serde::de::Error::custom)
323        } else {
324            #[derive(serde::Deserialize)]
325            struct GenericSignature(Vec<u8>);
326
327            let data = GenericSignature::deserialize(deserializer)?;
328            Self::from_bytes(&data.0).map_err(|e| Error::custom(e.to_string()))
329        }
330    }
331}
332
333/// This ports the wrapper trait to the verify_secure defined on [enum
334/// Signature].
335impl AuthenticatorTrait for Signature {
336    #[instrument(level = "trace", skip_all)]
337    fn verify_claims<T>(
338        &self,
339        value: &IntentMessage<T>,
340        author: Address,
341        _aux_verify_data: &VerifyParams,
342    ) -> IotaResult
343    where
344        T: Serialize,
345    {
346        self.verify_secure(value, author, self.scheme())
347    }
348}