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    error::FastCryptoError,
11    secp256k1::{Secp256k1PublicKey, Secp256k1Signature},
12    secp256r1::{Secp256r1PublicKey, Secp256r1Signature},
13    traits::{EncodeDecodeBase64, ToFromBytes},
14};
15use iota_sdk_types::crypto::IntentMessage;
16use serde::Serialize;
17use tracing::instrument;
18
19use crate::{
20    base_types::IotaAddress,
21    crypto::{
22        CompressedSignature, IotaSignature, PasskeyAuthenticatorAsBytes, PublicKey, Signature,
23        SignatureScheme,
24    },
25    error::{IotaError, IotaResult},
26    move_authenticator::{MoveAuthenticator, MoveAuthenticatorInner, MoveAuthenticatorV1},
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: IotaAddress,
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: IotaAddress,
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: IotaAddress,
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.as_ref().to_vec()),
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(s) => s.get_pk(),
225            GenericSignature::MoveAuthenticator(_) => Err(IotaError::UnsupportedFeature {
226                error: "Unsupported in MoveAuthenticator".to_string(),
227            }),
228            _ => Err(IotaError::UnsupportedFeature {
229                error: "Unsupported signature scheme".to_string(),
230            }),
231        }
232    }
233}
234
235/// GenericSignature encodes a single signature [enum Signature] as is `flag ||
236/// signature || pubkey`. [struct Multisig] is encoded as
237/// the MultiSig flag (0x03) concat with the bcs serialized bytes of [struct
238/// Multisig] i.e. `flag || bcs_bytes(Multisig)`.
239impl ToFromBytes for GenericSignature {
240    fn from_bytes(bytes: &[u8]) -> Result<Self, FastCryptoError> {
241        match SignatureScheme::from_flag_byte(
242            bytes.first().ok_or(FastCryptoError::InputTooShort(0))?,
243        ) {
244            Ok(x) => match x {
245                SignatureScheme::ED25519
246                | SignatureScheme::Secp256k1
247                | SignatureScheme::Secp256r1 => Ok(GenericSignature::Signature(
248                    Signature::from_bytes(bytes).map_err(|_| FastCryptoError::InvalidSignature)?,
249                )),
250                SignatureScheme::MultiSig => {
251                    Ok(GenericSignature::MultiSig(MultiSig::from_bytes(bytes)?))
252                }
253                #[allow(deprecated)]
254                SignatureScheme::ZkLoginAuthenticatorDeprecated => {
255                    // zkLogin is deprecated and was never enabled on IOTA — reject at
256                    // deserialization.
257                    Err(FastCryptoError::GeneralError(
258                        "zkLogin is not supported".to_string(),
259                    ))
260                }
261                SignatureScheme::PasskeyAuthenticator => {
262                    let passkey = PasskeyAuthenticator::from_bytes(bytes)?;
263                    Ok(GenericSignature::PasskeyAuthenticator(passkey))
264                }
265                SignatureScheme::MoveAuthenticator => {
266                    let move_auth = MoveAuthenticator::from_bytes(bytes)?;
267                    Ok(GenericSignature::MoveAuthenticator(move_auth))
268                }
269                _ => Err(FastCryptoError::InvalidInput),
270            },
271            Err(_) => Err(FastCryptoError::InvalidInput),
272        }
273    }
274}
275
276/// Trait useful to get the bytes reference for [enum GenericSignature].
277impl AsRef<[u8]> for GenericSignature {
278    fn as_ref(&self) -> &[u8] {
279        match self {
280            GenericSignature::MultiSig(s) => s.as_ref(),
281            GenericSignature::Signature(s) => s.as_ref(),
282            #[allow(deprecated)]
283            GenericSignature::ZkLoginAuthenticatorDeprecated(s) => s.as_ref(),
284            GenericSignature::PasskeyAuthenticator(s) => s.as_ref(),
285            GenericSignature::MoveAuthenticator(s) => s.as_ref(),
286        }
287    }
288}
289
290impl ::serde::Serialize for GenericSignature {
291    fn serialize<S: ::serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
292        if serializer.is_human_readable() {
293            #[derive(serde::Serialize)]
294            struct GenericSignature(String);
295            GenericSignature(self.encode_base64()).serialize(serializer)
296        } else {
297            #[derive(serde::Serialize)]
298            struct GenericSignature<'a>(&'a [u8]);
299            GenericSignature(self.as_ref()).serialize(serializer)
300        }
301    }
302}
303
304impl<'de> ::serde::Deserialize<'de> for GenericSignature {
305    fn deserialize<D: ::serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
306        use serde::de::Error;
307
308        if deserializer.is_human_readable() {
309            #[derive(serde::Deserialize)]
310            struct GenericSignature(String);
311            let s = GenericSignature::deserialize(deserializer)?;
312            Self::decode_base64(&s.0).map_err(::serde::de::Error::custom)
313        } else {
314            #[derive(serde::Deserialize)]
315            struct GenericSignature(Vec<u8>);
316
317            let data = GenericSignature::deserialize(deserializer)?;
318            Self::from_bytes(&data.0).map_err(|e| Error::custom(e.to_string()))
319        }
320    }
321}
322
323/// This ports the wrapper trait to the verify_secure defined on [enum
324/// Signature].
325impl AuthenticatorTrait for Signature {
326    #[instrument(level = "trace", skip_all)]
327    fn verify_claims<T>(
328        &self,
329        value: &IntentMessage<T>,
330        author: IotaAddress,
331        _aux_verify_data: &VerifyParams,
332    ) -> IotaResult
333    where
334        T: Serialize,
335    {
336        self.verify_secure(value, author, self.scheme())
337    }
338}