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