bc_components/signing/
signature.rs

1#[cfg(feature = "ed25519")]
2use bc_crypto::ED25519_SIGNATURE_SIZE;
3#[cfg(feature = "secp256k1")]
4use bc_crypto::{ECDSA_SIGNATURE_SIZE, SCHNORR_SIGNATURE_SIZE};
5use bc_ur::prelude::*;
6#[cfg(feature = "ssh")]
7use ssh_key::{LineEnding, SshSig};
8
9use super::SignatureScheme;
10#[cfg(any(feature = "secp256k1", feature = "ed25519", feature = "ssh"))]
11use crate::Error;
12#[cfg(feature = "pqcrypto")]
13use crate::MLDSASignature;
14use crate::{Result, tags};
15
16/// A digital signature created with various signature algorithms.
17///
18/// `Signature` is an enum representing different types of digital signatures:
19///
20/// - `Schnorr`: A BIP-340 Schnorr signature (64 bytes)
21/// - `ECDSA`: An ECDSA signature using the secp256k1 curve (64 bytes)
22/// - `Ed25519`: An Ed25519 signature (64 bytes)
23/// - `SSH`: An SSH signature in various formats
24/// - `MLDSA`: A post-quantum ML-DSA signature
25///
26/// Signatures can be serialized to and from CBOR with appropriate tags.
27///
28/// # Examples
29///
30/// ```ignore
31/// # // Requires secp256k1 feature (enabled by default)
32/// use bc_components::{SignatureScheme, Signer, Verifier};
33///
34/// // Create a key pair using Schnorr
35/// let (private_key, public_key) = SignatureScheme::Schnorr.keypair();
36///
37/// // Sign a message
38/// let message = b"Hello, world!";
39/// let signature = private_key.sign(&message).unwrap();
40///
41/// // The signature can be verified with the corresponding public key
42/// assert!(public_key.verify(&signature, &message));
43/// ```
44///
45/// Converting to and from CBOR:
46///
47/// ```ignore
48/// # // Requires secp256k1 feature (enabled by default)
49/// use bc_components::{SignatureScheme, Signer};
50/// use dcbor::prelude::*;
51///
52/// // Create a signature
53/// let (private_key, _) = SignatureScheme::Schnorr.keypair();
54/// let message = b"Hello, world!";
55/// let signature = private_key.sign(&message).unwrap();
56///
57/// // Convert to CBOR
58/// let cbor: CBOR = signature.clone().into();
59/// let data = cbor.to_cbor_data();
60///
61/// // Convert back from CBOR
62/// let recovered =
63///     bc_components::Signature::from_tagged_cbor_data(&data).unwrap();
64///
65/// // The signatures should be identical
66/// assert_eq!(signature, recovered);
67/// ```
68#[derive(Clone)]
69pub enum Signature {
70    /// A BIP-340 Schnorr signature (64 bytes)
71    #[cfg(feature = "secp256k1")]
72    Schnorr([u8; SCHNORR_SIGNATURE_SIZE]),
73
74    /// An ECDSA signature using the secp256k1 curve (64 bytes)
75    #[cfg(feature = "secp256k1")]
76    ECDSA([u8; ECDSA_SIGNATURE_SIZE]),
77
78    /// An Ed25519 signature (64 bytes)
79    #[cfg(feature = "ed25519")]
80    Ed25519([u8; ED25519_SIGNATURE_SIZE]),
81
82    /// An SSH signature
83    #[cfg(feature = "ssh")]
84    SSH(SshSig),
85
86    /// A post-quantum ML-DSA signature
87    #[cfg(feature = "pqcrypto")]
88    MLDSA(MLDSASignature),
89}
90
91/// Implementation of equality comparison for Signature
92impl PartialEq for Signature {
93    /// Compares two signatures for equality.
94    ///
95    /// Signatures are equal if they have the same type and the same signature
96    /// data. Signatures of different types (e.g., Schnorr vs ECDSA) are
97    /// never equal.
98    #[allow(unreachable_patterns)]
99    fn eq(&self, other: &Self) -> bool {
100        match (self, other) {
101            #[cfg(feature = "secp256k1")]
102            (Self::Schnorr(a), Self::Schnorr(b)) => a == b,
103            #[cfg(feature = "secp256k1")]
104            (Self::ECDSA(a), Self::ECDSA(b)) => a == b,
105            #[cfg(feature = "ed25519")]
106            (Self::Ed25519(a), Self::Ed25519(b)) => a == b,
107            #[cfg(feature = "ssh")]
108            (Self::SSH(a), Self::SSH(b)) => a == b,
109            #[cfg(feature = "pqcrypto")]
110            (Self::MLDSA(a), Self::MLDSA(b)) => a.as_bytes() == b.as_bytes(),
111            #[cfg(any(
112                feature = "secp256k1",
113                feature = "ed25519",
114                feature = "ssh",
115                feature = "pqcrypto"
116            ))]
117            _ => false,
118            #[cfg(not(any(
119                feature = "secp256k1",
120                feature = "ed25519",
121                feature = "ssh",
122                feature = "pqcrypto"
123            )))]
124            _ => unreachable!(),
125        }
126    }
127}
128
129impl Signature {
130    /// Creates a Schnorr signature from a 64-byte array.
131    ///
132    /// # Arguments
133    ///
134    /// * `data` - The 64-byte signature data
135    ///
136    /// # Returns
137    ///
138    /// A new Schnorr signature
139    ///
140    /// # Examples
141    ///
142    /// ```
143    /// # #[cfg(feature = "secp256k1")]
144    /// # {
145    /// use bc_components::Signature;
146    ///
147    /// let data = [0u8; 64]; // In practice, this would be a real signature
148    /// let signature = Signature::schnorr_from_data(data);
149    /// # }
150    /// ```
151    #[cfg(feature = "secp256k1")]
152    pub fn schnorr_from_data(data: [u8; SCHNORR_SIGNATURE_SIZE]) -> Self {
153        Self::Schnorr(data)
154    }
155
156    /// Creates a Schnorr signature from a byte slice.
157    ///
158    /// # Arguments
159    ///
160    /// * `data` - A byte slice containing the signature data
161    ///
162    /// # Returns
163    ///
164    /// A `Result` containing the signature or an error if the data is not
165    /// exactly 64 bytes in length.
166    ///
167    /// # Examples
168    ///
169    /// ```
170    /// # #[cfg(feature = "secp256k1")]
171    /// # {
172    /// use bc_components::Signature;
173    ///
174    /// let data = vec![0u8; 64]; // In practice, this would be a real signature
175    /// let signature = Signature::schnorr_from_data_ref(&data).unwrap();
176    /// # }
177    /// ```
178    #[cfg(feature = "secp256k1")]
179    pub fn schnorr_from_data_ref(data: impl AsRef<[u8]>) -> Result<Self> {
180        let data = data.as_ref();
181        if data.len() != SCHNORR_SIGNATURE_SIZE {
182            return Err(Error::invalid_size(
183                "Schnorr signature",
184                SCHNORR_SIGNATURE_SIZE,
185                data.len(),
186            ));
187        }
188        let mut arr = [0u8; SCHNORR_SIGNATURE_SIZE];
189        arr.copy_from_slice(data);
190        Ok(Self::schnorr_from_data(arr))
191    }
192
193    /// Creates an ECDSA signature from a 64-byte array.
194    ///
195    /// # Arguments
196    ///
197    /// * `data` - The 64-byte signature data
198    ///
199    /// # Returns
200    ///
201    /// A new ECDSA signature
202    ///
203    /// # Examples
204    ///
205    /// ```
206    /// # #[cfg(feature = "secp256k1")]
207    /// # {
208    /// use bc_components::Signature;
209    ///
210    /// let data = [0u8; 64]; // In practice, this would be a real signature
211    /// let signature = Signature::ecdsa_from_data(data);
212    /// # }
213    /// ```
214    #[cfg(feature = "secp256k1")]
215    pub fn ecdsa_from_data(data: [u8; ECDSA_SIGNATURE_SIZE]) -> Self {
216        Self::ECDSA(data)
217    }
218
219    /// Creates an ECDSA signature from a byte slice.
220    ///
221    /// # Arguments
222    ///
223    /// * `data` - A byte slice containing the signature data
224    ///
225    /// # Returns
226    ///
227    /// A `Result` containing the signature or an error if the data is not
228    /// exactly 64 bytes in length.
229    ///
230    /// # Examples
231    ///
232    /// ```
233    /// # #[cfg(feature = "secp256k1")]
234    /// # {
235    /// use bc_components::Signature;
236    ///
237    /// let data = vec![0u8; 64]; // In practice, this would be a real signature
238    /// let signature = Signature::ecdsa_from_data_ref(&data).unwrap();
239    /// # }
240    /// ```
241    #[cfg(feature = "secp256k1")]
242    pub fn ecdsa_from_data_ref(data: impl AsRef<[u8]>) -> Result<Self> {
243        let data = data.as_ref();
244        if data.len() != ECDSA_SIGNATURE_SIZE {
245            return Err(Error::invalid_size(
246                "ECDSA signature",
247                ECDSA_SIGNATURE_SIZE,
248                data.len(),
249            ));
250        }
251        let mut arr = [0u8; ECDSA_SIGNATURE_SIZE];
252        arr.copy_from_slice(data);
253        Ok(Self::ecdsa_from_data(arr))
254    }
255
256    /// Creates an Ed25519 signature from a 64-byte array.
257    ///
258    /// # Arguments
259    ///
260    /// * `data` - The 64-byte signature data
261    ///
262    /// # Returns
263    ///
264    /// A new Ed25519 signature
265    ///
266    /// # Examples
267    ///
268    /// ```
269    /// # #[cfg(feature = "ed25519")]
270    /// # {
271    /// use bc_components::Signature;
272    ///
273    /// let data = [0u8; 64]; // In practice, this would be a real signature
274    /// let signature = Signature::ed25519_from_data(data);
275    /// # }
276    /// ```
277    #[cfg(feature = "ed25519")]
278    pub fn ed25519_from_data(data: [u8; ED25519_SIGNATURE_SIZE]) -> Self {
279        Self::Ed25519(data)
280    }
281
282    /// Creates an Ed25519 signature from a byte slice.
283    ///
284    /// # Arguments
285    ///
286    /// * `data` - A byte slice containing the signature data
287    ///
288    /// # Returns
289    ///
290    /// A `Result` containing the signature or an error if the data is not
291    /// exactly 64 bytes in length.
292    ///
293    /// # Examples
294    ///
295    /// ```
296    /// # #[cfg(feature = "ed25519")]
297    /// # {
298    /// use bc_components::Signature;
299    ///
300    /// let data = vec![0u8; 64]; // In practice, this would be a real signature
301    /// let signature = Signature::ed25519_from_data_ref(&data).unwrap();
302    /// # }
303    /// ```
304    #[cfg(feature = "ed25519")]
305    pub fn ed25519_from_data_ref(data: impl AsRef<[u8]>) -> Result<Self> {
306        let data = data.as_ref();
307        if data.len() != ED25519_SIGNATURE_SIZE {
308            return Err(Error::invalid_size(
309                "Ed25519 signature",
310                ED25519_SIGNATURE_SIZE,
311                data.len(),
312            ));
313        }
314        let mut arr = [0u8; ED25519_SIGNATURE_SIZE];
315        arr.copy_from_slice(data);
316        Ok(Self::Ed25519(arr))
317    }
318
319    /// Creates an SSH signature from an `SshSig` object.
320    ///
321    /// # Arguments
322    ///
323    /// * `sig` - The SSH signature object
324    ///
325    /// # Returns
326    ///
327    /// A new SSH signature
328    #[cfg(feature = "ssh")]
329    pub fn from_ssh(sig: SshSig) -> Self { Self::SSH(sig) }
330
331    /// Returns the Schnorr signature data if this is a Schnorr signature.
332    ///
333    /// # Returns
334    ///
335    /// Some reference to the 64-byte signature data if this is a Schnorr
336    /// signature, or None if it's a different signature type.
337    ///
338    /// # Examples
339    ///
340    /// ```
341    /// # #[cfg(feature = "secp256k1")]
342    /// # {
343    /// use bc_components::{SignatureScheme, Signer};
344    ///
345    /// // Create a Schnorr signature
346    /// let (private_key, _) = SignatureScheme::Schnorr.keypair();
347    /// let message = b"Hello, world!";
348    /// let signature = private_key.sign(&message).unwrap();
349    ///
350    /// // We can access the Schnorr signature data
351    /// assert!(signature.to_schnorr().is_some());
352    ///
353    /// // Create an ECDSA signature
354    /// let (ecdsa_key, _) = SignatureScheme::Ecdsa.keypair();
355    /// let ecdsa_sig = ecdsa_key.sign(&message).unwrap();
356    ///
357    /// // This will return None since it's not a Schnorr signature
358    /// assert!(ecdsa_sig.to_schnorr().is_none());
359    /// # }
360    /// ```
361    #[cfg(feature = "secp256k1")]
362    pub fn to_schnorr(&self) -> Option<&[u8; SCHNORR_SIGNATURE_SIZE]> {
363        match self {
364            Self::Schnorr(sig) => Some(sig),
365            _ => None,
366        }
367    }
368
369    /// Returns the ECDSA signature data if this is an ECDSA signature.
370    ///
371    /// # Returns
372    ///
373    /// Some reference to the 64-byte signature data if this is an ECDSA
374    /// signature, or None if it's a different signature type.
375    #[cfg(feature = "secp256k1")]
376    pub fn to_ecdsa(&self) -> Option<&[u8; ECDSA_SIGNATURE_SIZE]> {
377        match self {
378            Self::ECDSA(sig) => Some(sig),
379            _ => None,
380        }
381    }
382
383    /// Returns the SSH signature if this is an SSH signature.
384    ///
385    /// # Returns
386    ///
387    /// Some reference to the SSH signature if this is an SSH signature,
388    /// or None if it's a different signature type.
389    #[cfg(feature = "ssh")]
390    pub fn to_ssh(&self) -> Option<&SshSig> {
391        match self {
392            Self::SSH(sig) => Some(sig),
393            #[cfg(any(
394                feature = "secp256k1",
395                feature = "ed25519",
396                feature = "pqcrypto"
397            ))]
398            _ => None,
399        }
400    }
401
402    /// Determines the signature scheme used to create this signature.
403    ///
404    /// # Returns
405    ///
406    /// A `Result` containing the signature scheme, or an error if the
407    /// signature scheme cannot be determined (e.g., for unsupported SSH
408    /// algorithms).
409    ///
410    /// # Examples
411    ///
412    /// ```ignore
413    /// # // Requires secp256k1 feature (enabled by default)
414    /// use bc_components::{SignatureScheme, Signer};
415    ///
416    /// // Create a signature with ECDSA
417    /// let (private_key, _) = SignatureScheme::Ecdsa.keypair();
418    /// let message = b"Hello, world!";
419    /// let signature = private_key.sign(&message).unwrap();
420    ///
421    /// // Get the signature scheme
422    /// let scheme = signature.scheme().unwrap();
423    /// assert_eq!(scheme, SignatureScheme::Ecdsa);
424    /// ```
425    #[allow(unreachable_patterns)]
426    pub fn scheme(&self) -> Result<SignatureScheme> {
427        match self {
428            #[cfg(feature = "secp256k1")]
429            Self::Schnorr(_) => Ok(SignatureScheme::Schnorr),
430            #[cfg(feature = "secp256k1")]
431            Self::ECDSA(_) => Ok(SignatureScheme::Ecdsa),
432            #[cfg(feature = "ed25519")]
433            Self::Ed25519(_) => Ok(SignatureScheme::Ed25519),
434            #[cfg(feature = "ssh")]
435            Self::SSH(sig) => match sig.algorithm() {
436                ssh_key::Algorithm::Dsa => Ok(SignatureScheme::SshDsa),
437                ssh_key::Algorithm::Ecdsa { curve } => match curve {
438                    ssh_key::EcdsaCurve::NistP256 => {
439                        Ok(SignatureScheme::SshEcdsaP256)
440                    }
441                    ssh_key::EcdsaCurve::NistP384 => {
442                        Ok(SignatureScheme::SshEcdsaP384)
443                    }
444                    _ => Err(Error::ssh("Unsupported SSH ECDSA curve")),
445                },
446                ssh_key::Algorithm::Ed25519 => Ok(SignatureScheme::SshEd25519),
447                _ => Err(Error::ssh("Unsupported SSH signature algorithm")),
448            },
449            #[cfg(feature = "ssh")]
450            Self::SSH(_) => unreachable!(),
451            #[cfg(feature = "pqcrypto")]
452            Self::MLDSA(sig) => match sig.level() {
453                crate::MLDSA::MLDSA44 => Ok(SignatureScheme::MLDSA44),
454                crate::MLDSA::MLDSA65 => Ok(SignatureScheme::MLDSA65),
455                crate::MLDSA::MLDSA87 => Ok(SignatureScheme::MLDSA87),
456            },
457            #[cfg(not(any(
458                feature = "secp256k1",
459                feature = "ed25519",
460                feature = "ssh",
461                feature = "pqcrypto"
462            )))]
463            _ => unreachable!(),
464        }
465    }
466}
467
468/// Debug implementation for Signature
469impl std::fmt::Debug for Signature {
470    /// Formats the signature for display.
471    ///
472    /// For binary signatures (Schnorr, ECDSA, Ed25519), displays the
473    /// hex-encoded signature data. For SSH and ML-DSA signatures, displays
474    /// the signature object.
475    #[allow(unreachable_patterns)]
476    fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
477        match self {
478            #[cfg(feature = "secp256k1")]
479            Signature::Schnorr(data) => _f
480                .debug_struct("Schnorr")
481                .field("data", &hex::encode(data))
482                .finish(),
483            #[cfg(feature = "secp256k1")]
484            Signature::ECDSA(data) => _f
485                .debug_struct("ECDSA")
486                .field("data", &hex::encode(data))
487                .finish(),
488            #[cfg(feature = "ed25519")]
489            Signature::Ed25519(data) => _f
490                .debug_struct("Ed25519")
491                .field("data", &hex::encode(data))
492                .finish(),
493            #[cfg(feature = "ssh")]
494            Signature::SSH(sig) => {
495                _f.debug_struct("SSH").field("sig", sig).finish()
496            }
497            #[cfg(feature = "pqcrypto")]
498            Signature::MLDSA(sig) => {
499                _f.debug_struct("MLDSA").field("sig", sig).finish()
500            }
501            #[cfg(not(any(
502                feature = "secp256k1",
503                feature = "ed25519",
504                feature = "ssh",
505                feature = "pqcrypto"
506            )))]
507            _ => unreachable!(),
508        }
509    }
510}
511
512/// Implementation of AsRef for Signature
513impl AsRef<Signature> for Signature {
514    /// Returns a reference to self.
515    fn as_ref(&self) -> &Signature { self }
516}
517
518/// Implementation of the CBORTagged trait for Signature
519impl CBORTagged for Signature {
520    /// Returns the CBOR tags used for this type.
521    ///
522    /// For Signature, the tag is 40020.
523    fn cbor_tags() -> Vec<dcbor::Tag> {
524        tags_for_values(&[tags::TAG_SIGNATURE])
525    }
526}
527
528/// Conversion from Signature to CBOR
529impl From<Signature> for CBOR {
530    /// Converts a Signature to a tagged CBOR value.
531    fn from(value: Signature) -> Self { value.tagged_cbor() }
532}
533
534/// Implementation of the CBORTaggedEncodable trait for Signature
535impl CBORTaggedEncodable for Signature {
536    /// Converts the Signature to an untagged CBOR value.
537    ///
538    /// The CBOR encoding depends on the signature type:
539    ///
540    /// - Schnorr: A byte string containing the 64-byte signature
541    /// - ECDSA: An array containing the discriminator 1 and the 64-byte
542    ///   signature
543    /// - Ed25519: An array containing the discriminator 2 and the 64-byte
544    ///   signature
545    /// - SSH: A tagged text string containing the PEM-encoded signature
546    /// - ML-DSA: Delegates to the MLDSASignature implementation
547    #[allow(unreachable_patterns)]
548    fn untagged_cbor(&self) -> CBOR {
549        match self {
550            #[cfg(feature = "secp256k1")]
551            Signature::Schnorr(data) => CBOR::to_byte_string(data),
552            #[cfg(feature = "secp256k1")]
553            Signature::ECDSA(data) => {
554                vec![(1).into(), CBOR::to_byte_string(data)].into()
555            }
556            #[cfg(feature = "ed25519")]
557            Signature::Ed25519(data) => {
558                vec![(2).into(), CBOR::to_byte_string(data)].into()
559            }
560            #[cfg(feature = "ssh")]
561            Signature::SSH(sig) => {
562                let pem = sig.to_pem(LineEnding::LF).unwrap();
563                CBOR::to_tagged_value(tags::TAG_SSH_TEXT_SIGNATURE, pem)
564            }
565            #[cfg(feature = "pqcrypto")]
566            Signature::MLDSA(sig) => sig.clone().into(),
567            #[cfg(not(any(
568                feature = "secp256k1",
569                feature = "ed25519",
570                feature = "ssh",
571                feature = "pqcrypto"
572            )))]
573            _ => unreachable!(),
574        }
575    }
576}
577
578/// TryFrom implementation for converting CBOR to Signature
579impl TryFrom<CBOR> for Signature {
580    type Error = dcbor::Error;
581
582    /// Tries to convert a CBOR value to a Signature.
583    ///
584    /// This is a convenience method that calls from_tagged_cbor.
585    fn try_from(cbor: CBOR) -> dcbor::Result<Self> {
586        Self::from_tagged_cbor(cbor)
587    }
588}
589
590/// Implementation of the CBORTaggedDecodable trait for Signature
591impl CBORTaggedDecodable for Signature {
592    /// Creates a Signature from an untagged CBOR value.
593    ///
594    /// # Arguments
595    ///
596    /// * `cbor` - The CBOR value to decode
597    ///
598    /// # Returns
599    ///
600    /// A Result containing the decoded Signature or an error if decoding fails.
601    ///
602    /// # Format
603    ///
604    /// The CBOR value must be one of:
605    /// - A byte string (interpreted as a Schnorr signature)
606    /// - An array of length 2, where the first element is 1 (ECDSA) or 2
607    ///   (Ed25519) and the second element is a byte string containing the
608    ///   signature data
609    /// - A tagged value with a tag for MLDSA or SSH signatures
610    fn from_untagged_cbor(cbor: CBOR) -> dcbor::Result<Self> {
611        match cbor.clone().into_case() {
612            CBORCase::ByteString(bytes) => {
613                #[cfg(feature = "secp256k1")]
614                {
615                    Ok(Self::schnorr_from_data_ref(bytes)?)
616                }
617                #[cfg(not(feature = "secp256k1"))]
618                {
619                    let _ = bytes;
620                    Err("Schnorr signature not available without secp256k1 feature".into())
621                }
622            }
623            CBORCase::Array(mut elements) => {
624                if elements.len() == 2 {
625                    let mut drain = elements.drain(0..);
626                    let ele_0 = drain.next().unwrap().into_case();
627                    #[cfg_attr(
628                        not(any(feature = "secp256k1", feature = "ed25519")),
629                        allow(unused_variables)
630                    )]
631                    let ele_1 = drain.next().unwrap().into_case();
632                    match ele_0 {
633                        #[cfg(feature = "secp256k1")]
634                        CBORCase::ByteString(data) => {
635                            return Ok(Self::schnorr_from_data_ref(data)?);
636                        }
637                        #[cfg(feature = "secp256k1")]
638                        CBORCase::Unsigned(1) => {
639                            if let CBORCase::ByteString(data) = ele_1 {
640                                return Ok(Self::ecdsa_from_data_ref(data)?);
641                            }
642                        }
643                        #[cfg(feature = "ed25519")]
644                        CBORCase::Unsigned(2) => {
645                            if let CBORCase::ByteString(data) = ele_1 {
646                                return Ok(Self::ed25519_from_data_ref(data)?);
647                            }
648                        }
649                        _ => (),
650                    }
651                }
652                Err("Invalid signature format".into())
653            }
654            #[cfg_attr(not(feature = "ssh"), allow(unused_variables))]
655            CBORCase::Tagged(tag, item) => match tag.value() {
656                #[cfg(feature = "pqcrypto")]
657                tags::TAG_MLDSA_SIGNATURE => {
658                    let sig = MLDSASignature::try_from(cbor)?;
659                    Ok(Self::MLDSA(sig))
660                }
661                #[cfg(feature = "ssh")]
662                tags::TAG_SSH_TEXT_SIGNATURE => {
663                    let string = item.try_into_text()?;
664                    let pem = SshSig::from_pem(string)
665                        .map_err(|_| "Invalid PEM format")?;
666                    Ok(Self::SSH(pem))
667                }
668                _ => Err("Invalid signature format".into()),
669            },
670            _ => Err("Invalid signature format".into()),
671        }
672    }
673}