webauthn_rp

WebAuthn RP library.
git clone https://git.philomathiclife.com/repos/webauthn_rp
Log | Files | Refs | README

register.rs (182359B)


      1 #[cfg(test)]
      2 mod tests;
      3 #[cfg(feature = "serde_relaxed")]
      4 use self::{
      5     super::ser_relaxed::{RelaxedClientDataJsonParser, SerdeJsonErr},
      6     ser_relaxed::{CustomRegistration, RegistrationRelaxed},
      7 };
      8 #[cfg(all(doc, feature = "bin"))]
      9 use super::super::bin::{Decode, Encode};
     10 #[cfg(feature = "bin")]
     11 use super::register::bin::{AaguidOwned, MetadataOwned};
     12 use super::{
     13     super::request::register::{FourToSixtyThree, ResidentKeyRequirement},
     14     AuthData, AuthDataContainer, AuthExtOutput, AuthRespErr, AuthResponse, AuthTransports,
     15     AuthenticatorAttachment, Backup, CborSuccess, ClientDataJsonParser as _, CollectedClientData,
     16     CredentialId, Flag, FromCbor, HmacSecretGet, HmacSecretGetErr, LimitedVerificationParser,
     17     ParsedAuthData, Response, SentChallenge, cbor,
     18     error::CollectedClientDataErr,
     19     register::error::{
     20         AaguidErr, AttestationErr, AttestationObjectErr, AttestedCredentialDataErr,
     21         AuthenticatorDataErr, AuthenticatorExtensionOutputErr, CompressedP256PubKeyErr,
     22         CompressedP384PubKeyErr, CoseKeyErr, Ed25519PubKeyErr, Ed25519SignatureErr,
     23         MlDsa44PubKeyErr, MlDsa65PubKeyErr, MlDsa87PubKeyErr, PubKeyErr, RsaPubKeyErr,
     24         UncompressedP256PubKeyErr, UncompressedP384PubKeyErr,
     25     },
     26 };
     27 #[cfg(doc)]
     28 use super::{
     29     super::{
     30         AuthenticatedCredential, RegisteredCredential,
     31         hash::hash_set::MaxLenHashSet,
     32         request::{
     33             Challenge, UserVerificationRequirement,
     34             auth::{
     35                 AuthenticationVerificationOptions, BackupStateReq,
     36                 PublicKeyCredentialRequestOptions,
     37             },
     38             register::{CoseAlgorithmIdentifier, Extension, RegistrationServerState},
     39         },
     40     },
     41     AuthenticatorTransport,
     42 };
     43 use core::{
     44     cmp::Ordering,
     45     convert::Infallible,
     46     fmt::{self, Display, Formatter},
     47 };
     48 use ed25519_dalek::{Signature, Verifier as _, VerifyingKey};
     49 use ml_dsa::{MlDsa44, MlDsa65, MlDsa87, Signature as MlDsaSignature, VerifyingKey as MlDsaVerKey};
     50 use p256::{
     51     AffinePoint as P256Affine, NistP256, Sec1Point as P256Pt,
     52     ecdsa::{DerSignature as P256Sig, VerifyingKey as P256VerKey},
     53     elliptic_curve::{Curve, common::typenum::ToInt as _, point::DecompressPoint as _},
     54 };
     55 use p384::{
     56     AffinePoint as P384Affine, NistP384, Sec1Point as P384Pt,
     57     ecdsa::{DerSignature as P384Sig, VerifyingKey as P384VerKey},
     58 };
     59 use rsa::{
     60     BoxedUint, RsaPublicKey,
     61     pkcs1v15::{self, VerifyingKey as RsaVerKey},
     62     sha2::{Sha256, digest::Digest as _},
     63 };
     64 #[cfg(all(doc, feature = "serde_relaxed"))]
     65 use serde::Deserialize;
     66 /// Contains functionality to (de)serialize data to a data store.
     67 #[cfg(feature = "bin")]
     68 pub mod bin;
     69 /// Contains error types.
     70 pub mod error;
     71 /// Contains functionality to deserialize data from a client.
     72 #[cfg(feature = "serde")]
     73 mod ser;
     74 /// Contains functionality to deserialize data from a client in a "relaxed" way.
     75 #[cfg(feature = "serde_relaxed")]
     76 pub mod ser_relaxed;
     77 /// [`credentialProtectionPolicy`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#dom-authenticationextensionsclientinputs-credentialprotectionpolicy).
     78 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
     79 pub enum CredentialProtectionPolicy {
     80     /// `credProtect` was not sent.
     81     None,
     82     /// [`userVerificationOptional`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationoptional).
     83     UserVerificationOptional,
     84     /// [`userVerificationOptionalWithCredentialIDList`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationoptionalwithcredentialidlist).
     85     UserVerificationOptionalWithCredentialIdList,
     86     /// [`userVerificationRequired`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationrequired).
     87     UserVerificationRequired,
     88 }
     89 impl Display for CredentialProtectionPolicy {
     90     #[inline]
     91     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
     92         f.write_str(match *self {
     93             Self::None => "no credential protection policy sent",
     94             Self::UserVerificationOptional => "user verification optional",
     95             Self::UserVerificationOptionalWithCredentialIdList => {
     96                 "user verification optional with credential ID list"
     97             }
     98             Self::UserVerificationRequired => "user verification required",
     99         })
    100     }
    101 }
    102 #[expect(clippy::too_long_first_doc_paragraph, reason = "false positive")]
    103 /// [`hmac-secret`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-hmac-secret-extension)
    104 /// and
    105 /// [`hmac-secret-mc`](https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#sctn-hmac-secret-make-cred-extension).
    106 ///
    107 /// Note `hmac-secret-mc` can only exist if `hmac-secret` exists with a value of `true`.
    108 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    109 pub enum HmacSecret {
    110     /// No `hmac-secret` extension.
    111     None,
    112     /// `hmac-secret` extension with a value of `false`.
    113     NotEnabled,
    114     /// `hmac-secret` extension with a value of `true`.
    115     Enabled,
    116     /// `hmac-secret` extension with a value of `true` and `hmac-secret-mc` contained one encrypted PRF output.
    117     One,
    118     /// `hmac-secret` extension with a value of `true` and `hmac-secret-mc` contained two encrypted PRF outputs.
    119     Two,
    120 }
    121 /// [Authenticator extension output](https://www.w3.org/TR/webauthn-3/#authenticator-extension-output).
    122 #[derive(Clone, Copy, Debug)]
    123 pub struct AuthenticatorExtensionOutput {
    124     /// [`credProtect`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-credProtect-extension).
    125     pub cred_protect: CredentialProtectionPolicy,
    126     /// [`hmac-secret`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-hmac-secret-extension)
    127     /// and
    128     /// [`hmac-secret-mc`](https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#sctn-hmac-secret-make-cred-extension).
    129     pub hmac_secret: HmacSecret,
    130     /// [`minPinLength`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-minpinlength-extension).
    131     pub min_pin_length: Option<FourToSixtyThree>,
    132 }
    133 impl AuthExtOutput for AuthenticatorExtensionOutput {
    134     fn missing(self) -> bool {
    135         matches!(self.cred_protect, CredentialProtectionPolicy::None)
    136             && matches!(self.hmac_secret, HmacSecret::None)
    137             && self.min_pin_length.is_none()
    138     }
    139 }
    140 /// [`AuthenticatorExtensionOutput`] extensions that are saved in [`StaticState`] because they are used during
    141 /// authentication ceremonies.
    142 #[derive(Clone, Copy, Debug)]
    143 pub struct AuthenticatorExtensionOutputStaticState {
    144     /// [`AuthenticatorExtensionOutput::cred_protect`].
    145     pub cred_protect: CredentialProtectionPolicy,
    146     /// [`AuthenticatorExtensionOutput::hmac_secret`].
    147     ///
    148     /// Note we only care about whether or not it has been enabled. Specifcally this is `None` iff
    149     /// [`HmacSecret::None`], `Some(false)` iff [`HmacSecret::NotEnabled`]; otherwise `Some(true)`.
    150     pub hmac_secret: Option<bool>,
    151 }
    152 /// [`AuthenticatorExtensionOutput`] extensions that are saved in [`Metadata`] because they are purely informative
    153 /// and not used during authentication ceremonies.
    154 #[derive(Clone, Copy, Debug)]
    155 pub struct AuthenticatorExtensionOutputMetadata {
    156     /// [`AuthenticatorExtensionOutput::min_pin_length`].
    157     pub min_pin_length: Option<FourToSixtyThree>,
    158 }
    159 impl From<AuthenticatorExtensionOutput> for AuthenticatorExtensionOutputMetadata {
    160     #[inline]
    161     fn from(value: AuthenticatorExtensionOutput) -> Self {
    162         Self {
    163             min_pin_length: value.min_pin_length,
    164         }
    165     }
    166 }
    167 impl From<AuthenticatorExtensionOutput> for AuthenticatorExtensionOutputStaticState {
    168     #[inline]
    169     fn from(value: AuthenticatorExtensionOutput) -> Self {
    170         Self {
    171             cred_protect: value.cred_protect,
    172             hmac_secret: match value.hmac_secret {
    173                 HmacSecret::None => None,
    174                 HmacSecret::NotEnabled => Some(false),
    175                 HmacSecret::Enabled | HmacSecret::One | HmacSecret::Two => Some(true),
    176             },
    177         }
    178     }
    179 }
    180 impl FromCbor<'_> for CredentialProtectionPolicy {
    181     type Err = AuthenticatorExtensionOutputErr;
    182     fn from_cbor(cbor: &[u8]) -> Result<CborSuccess<'_, Self>, Self::Err> {
    183         /// [`userVerificationOptional`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationoptional).
    184         const USER_VERIFICATION_OPTIONAL: u8 = cbor::ONE;
    185         /// [`userVerificationOptionalWithCredentialIDList`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationoptionalwithcredentialidlist).
    186         const USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST: u8 = cbor::TWO;
    187         /// [`userVerificationRequired`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationrequired).
    188         const USER_VERIFICATION_REQUIRED: u8 = cbor::THREE;
    189         /// `credProtect` key.
    190         const KEY: [u8; 12] = [
    191             cbor::TEXT_11,
    192             b'c',
    193             b'r',
    194             b'e',
    195             b'd',
    196             b'P',
    197             b'r',
    198             b'o',
    199             b't',
    200             b'e',
    201             b'c',
    202             b't',
    203         ];
    204         cbor.split_at_checked(KEY.len()).map_or(
    205             Ok(CborSuccess {
    206                 value: Self::None,
    207                 remaining: cbor,
    208             }),
    209             |(key, key_rem)| {
    210                 if key == KEY {
    211                     key_rem
    212                         .split_first()
    213                         .ok_or(AuthenticatorExtensionOutputErr::Len)
    214                         .and_then(|(uv, remaining)| {
    215                             match *uv {
    216                                 USER_VERIFICATION_OPTIONAL => Ok(Self::UserVerificationOptional),
    217                                 USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST => {
    218                                     Ok(Self::UserVerificationOptionalWithCredentialIdList)
    219                                 }
    220                                 USER_VERIFICATION_REQUIRED => Ok(Self::UserVerificationRequired),
    221                                 _ => Err(AuthenticatorExtensionOutputErr::CredProtectValue),
    222                             }
    223                             .map(|value| CborSuccess { value, remaining })
    224                         })
    225                 } else {
    226                     Ok(CborSuccess {
    227                         value: Self::None,
    228                         remaining: cbor,
    229                     })
    230                 }
    231             },
    232         )
    233     }
    234 }
    235 /// [`hmac-secret`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-hmac-secret-extension).
    236 enum HmacSecretEnabled {
    237     /// No `hmac-secret` extension.
    238     None,
    239     /// `hmac-secret` set to the contained `bool`.
    240     Val(bool),
    241 }
    242 impl FromCbor<'_> for HmacSecretEnabled {
    243     type Err = AuthenticatorExtensionOutputErr;
    244     fn from_cbor(cbor: &[u8]) -> Result<CborSuccess<'_, Self>, Self::Err> {
    245         cbor.split_at_checked(cbor::HMAC_SECRET.len()).map_or(
    246             Ok(CborSuccess {
    247                 value: Self::None,
    248                 remaining: cbor,
    249             }),
    250             |(key, key_rem)| {
    251                 if key == cbor::HMAC_SECRET {
    252                     key_rem
    253                         .split_first()
    254                         .ok_or(AuthenticatorExtensionOutputErr::Len)
    255                         .and_then(|(hmac, remaining)| {
    256                             match *hmac {
    257                                 cbor::SIMPLE_FALSE => Ok(Self::Val(false)),
    258                                 cbor::SIMPLE_TRUE => Ok(Self::Val(true)),
    259                                 _ => Err(AuthenticatorExtensionOutputErr::HmacSecretValue),
    260                             }
    261                             .map(|value| CborSuccess { value, remaining })
    262                         })
    263                 } else {
    264                     Ok(CborSuccess {
    265                         value: Self::None,
    266                         remaining: cbor,
    267                     })
    268                 }
    269             },
    270         )
    271     }
    272 }
    273 /// [`minPinLength`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-minpinlength-extension).
    274 enum MinPinLength {
    275     /// No `minPinLength` extension.
    276     None,
    277     /// `minPinLength` with the value of the contained `FourToSixtyThree`.
    278     Val(FourToSixtyThree),
    279 }
    280 impl FromCbor<'_> for MinPinLength {
    281     type Err = AuthenticatorExtensionOutputErr;
    282     fn from_cbor(cbor: &[u8]) -> Result<CborSuccess<'_, Self>, Self::Err> {
    283         /// `minPinLength` key.
    284         const KEY: [u8; 13] = [
    285             cbor::TEXT_12,
    286             b'm',
    287             b'i',
    288             b'n',
    289             b'P',
    290             b'i',
    291             b'n',
    292             b'L',
    293             b'e',
    294             b'n',
    295             b'g',
    296             b't',
    297             b'h',
    298         ];
    299         cbor.split_at_checked(KEY.len()).map_or(
    300             Ok(CborSuccess {
    301                 value: Self::None,
    302                 remaining: cbor,
    303             }),
    304             |(key, key_rem)| {
    305                 if key == KEY {
    306                     key_rem
    307                         .split_first()
    308                         .ok_or(AuthenticatorExtensionOutputErr::Len)
    309                         .and_then(|(&key_len, remaining)| match key_len.cmp(&24) {
    310                             Ordering::Less => FourToSixtyThree::from_u8(key_len)
    311                                 .ok_or(AuthenticatorExtensionOutputErr::MinPinLengthValue)
    312                                 .map(|val| CborSuccess {
    313                                     value: Self::Val(val),
    314                                     remaining,
    315                                 }),
    316                             Ordering::Equal => remaining
    317                                 .split_first()
    318                                 .ok_or(AuthenticatorExtensionOutputErr::Len)
    319                                 .and_then(|(&key_24, rem)| {
    320                                     if key_24 > 23 {
    321                                         FourToSixtyThree::from_u8(key_24)
    322                                             .ok_or(
    323                                                 AuthenticatorExtensionOutputErr::MinPinLengthValue,
    324                                             )
    325                                             .map(|val| CborSuccess {
    326                                                 value: Self::Val(val),
    327                                                 remaining: rem,
    328                                             })
    329                                     } else {
    330                                         Err(AuthenticatorExtensionOutputErr::MinPinLengthValue)
    331                                     }
    332                                 }),
    333                             Ordering::Greater => {
    334                                 Err(AuthenticatorExtensionOutputErr::MinPinLengthValue)
    335                             }
    336                         })
    337                 } else {
    338                     Ok(CborSuccess {
    339                         value: Self::None,
    340                         remaining: cbor,
    341                     })
    342                 }
    343             },
    344         )
    345     }
    346 }
    347 impl From<HmacSecretGetErr> for AuthenticatorExtensionOutputErr {
    348     #[inline]
    349     fn from(value: HmacSecretGetErr) -> Self {
    350         match value {
    351             HmacSecretGetErr::Len => Self::Len,
    352             HmacSecretGetErr::Type => Self::HmacSecretMcType,
    353             HmacSecretGetErr::Value => Self::HmacSecretMcValue,
    354         }
    355     }
    356 }
    357 impl FromCbor<'_> for AuthenticatorExtensionOutput {
    358     type Err = AuthenticatorExtensionOutputErr;
    359     #[expect(
    360         clippy::too_many_lines,
    361         reason = "don't want to move logic into outer scope"
    362     )]
    363     fn from_cbor(cbor: &[u8]) -> Result<CborSuccess<'_, Self>, Self::Err> {
    364         // We don't allow unsupported extensions; thus the only possibilities is any ordered element of
    365         // the power set of {"credProtect":<1, 2, or 3>, "hmac-secret":<true or false>, "minPinLength":<0-255>,
    366         // "hmac-secret-mc":<48|80 bytes>}.
    367         // Since the keys are the same type (text), order is first done based on length; and then
    368         // byte-wise lexical order is followed; thus `credProtect` must come before `hmac-secret` which
    369         // must come before `minPinLength` which comes before `hmac-secret-mc`.
    370         //
    371         // Note `hmac-secret-mc` can only exist if `hmac-secret` exists with a value of `true`.
    372         let mut cred_protect = CredentialProtectionPolicy::None;
    373         let mut hmac_secret = HmacSecret::None;
    374         let mut min_pin_length = None;
    375         let mut remaining = cbor;
    376         cbor.split_first().map_or(
    377             Ok(()),
    378             |(map, map_rem)| {
    379                 match *map {
    380                     cbor::MAP_1 => {
    381                         CredentialProtectionPolicy::from_cbor(map_rem).and_then(|cred_success| {
    382                             if matches!(cred_success.value, CredentialProtectionPolicy::None) {
    383                                 HmacSecretEnabled::from_cbor(cred_success.remaining).and_then(
    384                                     |hmac_success| if let HmacSecretEnabled::Val(hmac) = hmac_success.value {
    385                                         hmac_secret = if hmac {
    386                                             HmacSecret::Enabled
    387                                         } else {
    388                                             HmacSecret::NotEnabled
    389                                         };
    390                                         remaining = hmac_success.remaining;
    391                                         Ok(())
    392                                     } else {
    393                                         MinPinLength::from_cbor(hmac_success.remaining).and_then(|pin_success| if let MinPinLength::Val(min_pin_len) = pin_success.value {
    394                                             min_pin_length = Some(min_pin_len);
    395                                             remaining = pin_success.remaining;
    396                                             Ok(())
    397                                         } else {
    398                                             // We don't even bother checking for `HmacSecretGet` since
    399                                             // it's only valid when `HmacSecretEnabled` exists with a value
    400                                             // of `true`.
    401                                             Err(AuthenticatorExtensionOutputErr::Missing)
    402                                         })
    403                                     }
    404                                 )
    405                             } else {
    406                                 cred_protect = cred_success.value;
    407                                 remaining = cred_success.remaining;
    408                                 Ok(())
    409                             }
    410                         })
    411                     }
    412                     cbor::MAP_2 => {
    413                         CredentialProtectionPolicy::from_cbor(map_rem).and_then(|cred_success| {
    414                             if matches!(cred_success.value, CredentialProtectionPolicy::None) {
    415                                 HmacSecretEnabled::from_cbor(cred_success.remaining).and_then(|hmac_success| if let HmacSecretEnabled::Val(hmac) = hmac_success.value {
    416                                     MinPinLength::from_cbor(hmac_success.remaining).and_then(|pin_success| if let MinPinLength::Val(min_pin_len) = pin_success.value {
    417                                         hmac_secret = if hmac {
    418                                             HmacSecret::Enabled
    419                                         } else {
    420                                             HmacSecret::NotEnabled
    421                                         };
    422                                         min_pin_length = Some(min_pin_len);
    423                                         remaining = pin_success.remaining;
    424                                         Ok(())
    425                                     } else if hmac {
    426                                         HmacSecretGet::<true>::from_cbor(pin_success.remaining).map_err(AuthenticatorExtensionOutputErr::from).and_then(|hmac_get| match hmac_get.value {
    427                                             HmacSecretGet::None => Err(AuthenticatorExtensionOutputErr::Missing),
    428                                             HmacSecretGet::One => {
    429                                                 hmac_secret = HmacSecret::One;
    430                                                 remaining = hmac_get.remaining;
    431                                                 Ok(())
    432                                             },
    433                                             HmacSecretGet::Two => {
    434                                                 hmac_secret = HmacSecret::Two;
    435                                                 remaining = hmac_get.remaining;
    436                                                 Ok(())
    437                                             },
    438                                         })
    439                                     } else {
    440                                         // We don't even bother checking for `HmacSecretGet` since
    441                                         // it's only valid when `HmacSecretEnabled` exists with a value
    442                                         // of `true`.
    443                                         Err(AuthenticatorExtensionOutputErr::Missing)
    444                                     })
    445                                 } else {
    446                                     // We don't even bother checking for `HmacSecretGet` since
    447                                     // it's only valid when `HmacSecretEnabled` exists with a value
    448                                     // of `true`.
    449                                     Err(AuthenticatorExtensionOutputErr::Missing)
    450                                 })
    451                             } else {
    452                                 HmacSecretEnabled::from_cbor(cred_success.remaining).and_then(|hmac_success| if let HmacSecretEnabled::Val(hmac) = hmac_success.value {
    453                                     cred_protect = cred_success.value;
    454                                     hmac_secret = if hmac {
    455                                         HmacSecret::Enabled
    456                                     } else {
    457                                         HmacSecret::NotEnabled
    458                                     };
    459                                     remaining = hmac_success.remaining;
    460                                     Ok(())
    461                                 } else {
    462                                     MinPinLength::from_cbor(hmac_success.remaining).and_then(|pin_success| if let MinPinLength::Val(min_pin_len) = pin_success.value {
    463                                         cred_protect = cred_success.value;
    464                                         min_pin_length = Some(min_pin_len);
    465                                         remaining = pin_success.remaining;
    466                                         Ok(())
    467                                     } else {
    468                                         // We don't even bother checking for `HmacSecretGet` since
    469                                         // it's only valid when `HmacSecretEnabled` exists with a value
    470                                         // of `true`.
    471                                         Err(AuthenticatorExtensionOutputErr::Missing)
    472                                     })
    473                                 })
    474                             }
    475                         })
    476                     }
    477                     cbor::MAP_3 => {
    478                         CredentialProtectionPolicy::from_cbor(map_rem).and_then(|cred_success| {
    479                             if matches!(cred_success.value, CredentialProtectionPolicy::None) {
    480                                 HmacSecretEnabled::from_cbor(cred_success.remaining).and_then(|hmac_success| if let HmacSecretEnabled::Val(hmac) = hmac_success.value && hmac {
    481                                     MinPinLength::from_cbor(hmac_success.remaining).and_then(|pin_success| if let MinPinLength::Val(min_pin_len) = pin_success.value {
    482                                         HmacSecretGet::<true>::from_cbor(pin_success.remaining).map_err(AuthenticatorExtensionOutputErr::from).and_then(|hmac_get| match hmac_get.value {
    483                                             HmacSecretGet::None => Err(AuthenticatorExtensionOutputErr::Missing),
    484                                             HmacSecretGet::One => {
    485                                                 hmac_secret = HmacSecret::One;
    486                                                 min_pin_length = Some(min_pin_len);
    487                                                 remaining = hmac_get.remaining;
    488                                                 Ok(())
    489                                             },
    490                                             HmacSecretGet::Two => {
    491                                                 hmac_secret = HmacSecret::Two;
    492                                                 min_pin_length = Some(min_pin_len);
    493                                                 remaining = hmac_get.remaining;
    494                                                 Ok(())
    495                                             },
    496                                         })
    497                                     } else {
    498                                         Err(AuthenticatorExtensionOutputErr::Missing)
    499                                     })
    500                                 } else {
    501                                     // We don't even bother checking for `HmacSecretGet` since
    502                                     // it's only valid when `HmacSecretEnabled` exists with a value
    503                                     // of `true`.
    504                                     Err(AuthenticatorExtensionOutputErr::Missing)
    505                                 })
    506                             } else {
    507                                 HmacSecretEnabled::from_cbor(cred_success.remaining).and_then(|hmac_success| if let HmacSecretEnabled::Val(hmac) = hmac_success.value {
    508                                     MinPinLength::from_cbor(hmac_success.remaining).and_then(|pin_success| if let MinPinLength::Val(min_pin_len) = pin_success.value {
    509                                         cred_protect = cred_success.value;
    510                                         hmac_secret = if hmac { HmacSecret::Enabled } else { HmacSecret::NotEnabled };
    511                                         min_pin_length = Some(min_pin_len);
    512                                         remaining = pin_success.remaining;
    513                                         Ok(())
    514                                     } else if hmac {
    515                                         HmacSecretGet::<true>::from_cbor(pin_success.remaining).map_err(AuthenticatorExtensionOutputErr::from).and_then(|hmac_get| match hmac_get.value {
    516                                             HmacSecretGet::None => Err(AuthenticatorExtensionOutputErr::Missing),
    517                                             HmacSecretGet::One => {
    518                                                 cred_protect = cred_success.value;
    519                                                 hmac_secret = HmacSecret::One;
    520                                                 remaining = hmac_get.remaining;
    521                                                 Ok(())
    522                                             }
    523                                             HmacSecretGet::Two => {
    524                                                 cred_protect = cred_success.value;
    525                                                 hmac_secret = HmacSecret::Two;
    526                                                 remaining = hmac_get.remaining;
    527                                                 Ok(())
    528                                             }
    529                                         })
    530                                     } else{
    531                                         // We don't even bother checking for `HmacSecretGet` since
    532                                         // it's only valid when `HmacSecretEnabled` exists with a value
    533                                         // of `true`.
    534                                         Err(AuthenticatorExtensionOutputErr::Missing)
    535                                     })
    536                                 } else {
    537                                     // We don't even bother checking for `HmacSecretGet` since
    538                                     // it's only valid when `HmacSecretEnabled` exists with a value
    539                                     // of `true`.
    540                                     Err(AuthenticatorExtensionOutputErr::Missing)
    541                                 })
    542                             }
    543                         })
    544                     }
    545                     cbor::MAP_4 => {
    546                         CredentialProtectionPolicy::from_cbor(map_rem).and_then(|cred_success| {
    547                             if matches!(cred_success.value, CredentialProtectionPolicy::None) {
    548                                 Err(AuthenticatorExtensionOutputErr::Missing)
    549                             } else {
    550                                 HmacSecretEnabled::from_cbor(cred_success.remaining).and_then(|hmac_success| if let HmacSecretEnabled::Val(hmac) = hmac_success.value && hmac {
    551                                     MinPinLength::from_cbor(hmac_success.remaining).and_then(|pin_success| if let MinPinLength::Val(min_pin_len) = pin_success.value {
    552                                         HmacSecretGet::<true>::from_cbor(pin_success.remaining).map_err(AuthenticatorExtensionOutputErr::from).and_then(|hmac_get| match hmac_get.value {
    553                                             HmacSecretGet::None => Err(AuthenticatorExtensionOutputErr::Missing),
    554                                             HmacSecretGet::One => {
    555                                                 cred_protect = cred_success.value;
    556                                                 hmac_secret = HmacSecret::One;
    557                                                 min_pin_length = Some(min_pin_len);
    558                                                 remaining = hmac_get.remaining;
    559                                                 Ok(())
    560                                             },
    561                                             HmacSecretGet::Two => {
    562                                                 cred_protect = cred_success.value;
    563                                                 hmac_secret = HmacSecret::Two;
    564                                                 min_pin_length = Some(min_pin_len);
    565                                                 remaining = hmac_get.remaining;
    566                                                 Ok(())
    567                                             },
    568                                         })
    569                                     } else {
    570                                         Err(AuthenticatorExtensionOutputErr::Missing)
    571                                     })
    572                                 } else {
    573                                     // We don't even bother checking for `HmacSecretGet` since
    574                                     // it's only valid when `HmacSecretEnabled` exists with a value
    575                                     // of `true`.
    576                                     Err(AuthenticatorExtensionOutputErr::Missing)
    577                                 })
    578                             }
    579                         })
    580                     }
    581                     _ => Err(AuthenticatorExtensionOutputErr::CborHeader),
    582                 }
    583             }
    584         ).map(|()| CborSuccess { value: Self { cred_protect, hmac_secret, min_pin_length }, remaining })
    585     }
    586 }
    587 /// 2592 bytes representing an alleged ML-DSA-87 public key.
    588 #[derive(Clone, Copy, Debug)]
    589 pub struct MlDsa87PubKey<T>(T);
    590 impl<T> MlDsa87PubKey<T> {
    591     /// Returns the contained data consuming `self`.
    592     #[inline]
    593     pub fn into_inner(self) -> T {
    594         self.0
    595     }
    596     /// Returns the contained data.
    597     #[inline]
    598     pub const fn inner(&self) -> &T {
    599         &self.0
    600     }
    601 }
    602 impl<T: AsRef<[u8]>> MlDsa87PubKey<T> {
    603     /// Returns the contained data.
    604     #[inline]
    605     #[must_use]
    606     pub fn encoded_data(&self) -> &[u8] {
    607         self.0.as_ref()
    608     }
    609 }
    610 impl MlDsa87PubKey<&[u8]> {
    611     /// Converts `self` into an [`MlDsaVerKey`].
    612     pub(super) fn into_ver_key(self) -> MlDsaVerKey<MlDsa87> {
    613         self.into_owned().into_ver_key()
    614     }
    615     /// Transforms `self` into an "owned" version.
    616     #[inline]
    617     #[must_use]
    618     pub fn into_owned(self) -> MlDsa87PubKey<Box<[u8]>> {
    619         MlDsa87PubKey(self.0.into())
    620     }
    621 }
    622 impl MlDsa87PubKey<Box<[u8]>> {
    623     /// Converts `self` into [`MlDsaVerKey`].
    624     #[expect(clippy::unreachable, reason = "want to crash when there is a bug")]
    625     fn into_ver_key(self) -> MlDsaVerKey<MlDsa87> {
    626         MlDsaVerKey::decode(
    627             self.0
    628                 .as_array()
    629                 .unwrap_or_else(|| unreachable!("there is a bug in slice::as_array"))
    630                 .into(),
    631         )
    632     }
    633 }
    634 impl<'a: 'b, 'b> TryFrom<&'a [u8]> for MlDsa87PubKey<&'b [u8]> {
    635     type Error = MlDsa87PubKeyErr;
    636     /// Interprets `value` as an encoded ML-DSA-87 public key.
    637     #[inline]
    638     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
    639         if value.len() == 2592 {
    640             Ok(Self(value))
    641         } else {
    642             Err(MlDsa87PubKeyErr)
    643         }
    644     }
    645 }
    646 impl TryFrom<Box<[u8]>> for MlDsa87PubKey<Box<[u8]>> {
    647     type Error = MlDsa87PubKeyErr;
    648     /// Interprets `value` as an encoded ML-DSA-87 public key.
    649     #[inline]
    650     fn try_from(value: Box<[u8]>) -> Result<Self, Self::Error> {
    651         if value.len() == 2592 {
    652             Ok(Self(value))
    653         } else {
    654             Err(MlDsa87PubKeyErr)
    655         }
    656     }
    657 }
    658 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<MlDsa87PubKey<T>> for MlDsa87PubKey<T2> {
    659     #[inline]
    660     fn eq(&self, other: &MlDsa87PubKey<T>) -> bool {
    661         self.0 == other.0
    662     }
    663 }
    664 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<MlDsa87PubKey<T>> for &MlDsa87PubKey<T2> {
    665     #[inline]
    666     fn eq(&self, other: &MlDsa87PubKey<T>) -> bool {
    667         **self == *other
    668     }
    669 }
    670 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<&MlDsa87PubKey<T>> for MlDsa87PubKey<T2> {
    671     #[inline]
    672     fn eq(&self, other: &&MlDsa87PubKey<T>) -> bool {
    673         *self == **other
    674     }
    675 }
    676 impl<T: Eq> Eq for MlDsa87PubKey<T> {}
    677 /// 1952 bytes representing an alleged ML-DSA-65 public key.
    678 #[derive(Clone, Copy, Debug)]
    679 pub struct MlDsa65PubKey<T>(T);
    680 impl<T> MlDsa65PubKey<T> {
    681     /// Returns the contained data consuming `self`.
    682     #[inline]
    683     pub fn into_inner(self) -> T {
    684         self.0
    685     }
    686     /// Returns the contained data.
    687     #[inline]
    688     pub const fn inner(&self) -> &T {
    689         &self.0
    690     }
    691 }
    692 impl<T: AsRef<[u8]>> MlDsa65PubKey<T> {
    693     /// Returns the contained data.
    694     #[inline]
    695     #[must_use]
    696     pub fn encoded_data(&self) -> &[u8] {
    697         self.0.as_ref()
    698     }
    699 }
    700 impl MlDsa65PubKey<&[u8]> {
    701     /// Converts `self` into an [`MlDsaVerKey`].
    702     pub(super) fn into_ver_key(self) -> MlDsaVerKey<MlDsa65> {
    703         self.into_owned().into_ver_key()
    704     }
    705     /// Transforms `self` into an "owned" version.
    706     #[inline]
    707     #[must_use]
    708     pub fn into_owned(self) -> MlDsa65PubKey<Box<[u8]>> {
    709         MlDsa65PubKey(self.0.into())
    710     }
    711 }
    712 impl MlDsa65PubKey<Box<[u8]>> {
    713     /// Converts `self` into [`MlDsaVerKey`].
    714     #[expect(clippy::unreachable, reason = "want to crash when there is a bug")]
    715     fn into_ver_key(self) -> MlDsaVerKey<MlDsa65> {
    716         MlDsaVerKey::decode(
    717             self.0
    718                 .as_array()
    719                 .unwrap_or_else(|| unreachable!("there is a bug in slice::as_array"))
    720                 .into(),
    721         )
    722     }
    723 }
    724 impl<'a: 'b, 'b> TryFrom<&'a [u8]> for MlDsa65PubKey<&'b [u8]> {
    725     type Error = MlDsa65PubKeyErr;
    726     /// Interprets `value` as an encoded ML-DSA-65 public key.
    727     #[inline]
    728     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
    729         if value.len() == 1952 {
    730             Ok(Self(value))
    731         } else {
    732             Err(MlDsa65PubKeyErr)
    733         }
    734     }
    735 }
    736 impl TryFrom<Box<[u8]>> for MlDsa65PubKey<Box<[u8]>> {
    737     type Error = MlDsa65PubKeyErr;
    738     /// Interprets `value` as an encoded ML-DSA-65 public key.
    739     #[inline]
    740     fn try_from(value: Box<[u8]>) -> Result<Self, Self::Error> {
    741         if value.len() == 1952 {
    742             Ok(Self(value))
    743         } else {
    744             Err(MlDsa65PubKeyErr)
    745         }
    746     }
    747 }
    748 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<MlDsa65PubKey<T>> for MlDsa65PubKey<T2> {
    749     #[inline]
    750     fn eq(&self, other: &MlDsa65PubKey<T>) -> bool {
    751         self.0 == other.0
    752     }
    753 }
    754 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<MlDsa65PubKey<T>> for &MlDsa65PubKey<T2> {
    755     #[inline]
    756     fn eq(&self, other: &MlDsa65PubKey<T>) -> bool {
    757         **self == *other
    758     }
    759 }
    760 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<&MlDsa65PubKey<T>> for MlDsa65PubKey<T2> {
    761     #[inline]
    762     fn eq(&self, other: &&MlDsa65PubKey<T>) -> bool {
    763         *self == **other
    764     }
    765 }
    766 impl<T: Eq> Eq for MlDsa65PubKey<T> {}
    767 /// 1312 bytes representing an alleged ML-DSA-44 public key.
    768 #[derive(Clone, Copy, Debug)]
    769 pub struct MlDsa44PubKey<T>(T);
    770 impl<T> MlDsa44PubKey<T> {
    771     /// Returns the contained data consuming `self`.
    772     #[inline]
    773     pub fn into_inner(self) -> T {
    774         self.0
    775     }
    776     /// Returns the contained data.
    777     #[inline]
    778     pub const fn inner(&self) -> &T {
    779         &self.0
    780     }
    781 }
    782 impl<T: AsRef<[u8]>> MlDsa44PubKey<T> {
    783     /// Returns the contained data.
    784     #[inline]
    785     #[must_use]
    786     pub fn encoded_data(&self) -> &[u8] {
    787         self.0.as_ref()
    788     }
    789 }
    790 impl MlDsa44PubKey<&[u8]> {
    791     /// Converts `self` into an [`MlDsaVerKey`].
    792     pub(super) fn into_ver_key(self) -> MlDsaVerKey<MlDsa44> {
    793         self.into_owned().into_ver_key()
    794     }
    795     /// Transforms `self` into an "owned" version.
    796     #[inline]
    797     #[must_use]
    798     pub fn into_owned(self) -> MlDsa44PubKey<Box<[u8]>> {
    799         MlDsa44PubKey(self.0.into())
    800     }
    801 }
    802 impl MlDsa44PubKey<Box<[u8]>> {
    803     /// Converts `self` into [`MlDsaVerKey`].
    804     #[expect(clippy::unreachable, reason = "want to crash when there is a bug")]
    805     fn into_ver_key(self) -> MlDsaVerKey<MlDsa44> {
    806         MlDsaVerKey::decode(
    807             self.0
    808                 .as_array()
    809                 .unwrap_or_else(|| unreachable!("there is a bug in slice::as_array"))
    810                 .into(),
    811         )
    812     }
    813 }
    814 impl<'a: 'b, 'b> TryFrom<&'a [u8]> for MlDsa44PubKey<&'b [u8]> {
    815     type Error = MlDsa44PubKeyErr;
    816     /// Interprets `value` as an encoded ML-DSA-44 public key.
    817     #[inline]
    818     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
    819         if value.len() == 1312 {
    820             Ok(Self(value))
    821         } else {
    822             Err(MlDsa44PubKeyErr)
    823         }
    824     }
    825 }
    826 impl TryFrom<Box<[u8]>> for MlDsa44PubKey<Box<[u8]>> {
    827     type Error = MlDsa44PubKeyErr;
    828     /// Interprets `value` as an encoded ML-DSA-44 public key.
    829     #[inline]
    830     fn try_from(value: Box<[u8]>) -> Result<Self, Self::Error> {
    831         if value.len() == 1312 {
    832             Ok(Self(value))
    833         } else {
    834             Err(MlDsa44PubKeyErr)
    835         }
    836     }
    837 }
    838 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<MlDsa44PubKey<T>> for MlDsa44PubKey<T2> {
    839     #[inline]
    840     fn eq(&self, other: &MlDsa44PubKey<T>) -> bool {
    841         self.0 == other.0
    842     }
    843 }
    844 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<MlDsa44PubKey<T>> for &MlDsa44PubKey<T2> {
    845     #[inline]
    846     fn eq(&self, other: &MlDsa44PubKey<T>) -> bool {
    847         **self == *other
    848     }
    849 }
    850 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<&MlDsa44PubKey<T>> for MlDsa44PubKey<T2> {
    851     #[inline]
    852     fn eq(&self, other: &&MlDsa44PubKey<T>) -> bool {
    853         *self == **other
    854     }
    855 }
    856 impl<T: Eq> Eq for MlDsa44PubKey<T> {}
    857 /// 32-bytes representing an alleged Ed25519 public key (i.e., compressed y-coordinate).
    858 #[derive(Clone, Copy, Debug)]
    859 pub struct Ed25519PubKey<T>(T);
    860 impl<T> Ed25519PubKey<T> {
    861     /// Returns the contained data consuming `self`.
    862     #[inline]
    863     pub fn into_inner(self) -> T {
    864         self.0
    865     }
    866     /// Returns the contained data.
    867     #[inline]
    868     pub const fn inner(&self) -> &T {
    869         &self.0
    870     }
    871 }
    872 impl<T: AsRef<[u8]>> Ed25519PubKey<T> {
    873     /// Returns the compressed y-coordinate.
    874     #[inline]
    875     #[must_use]
    876     pub fn compressed_y_coordinate(&self) -> &[u8] {
    877         self.0.as_ref()
    878     }
    879 }
    880 impl Ed25519PubKey<&[u8]> {
    881     /// Validates `self` is in fact a valid Ed25519 public key.
    882     ///
    883     /// # Errors
    884     ///
    885     /// Errors iff `self` is not a valid Ed25519 public key.
    886     #[inline]
    887     pub fn validate(self) -> Result<(), PubKeyErr> {
    888         self.into_ver_key().map(|_| ())
    889     }
    890     /// Converts `self` into [`VerifyingKey`].
    891     pub(super) fn into_ver_key(self) -> Result<VerifyingKey, PubKeyErr> {
    892         self.into_owned().into_ver_key()
    893     }
    894     /// Transforms `self` into an "owned" version.
    895     #[inline]
    896     #[must_use]
    897     pub fn into_owned(self) -> Ed25519PubKey<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]> {
    898         Ed25519PubKey(*Ed25519PubKey::<&[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>::from(self).0)
    899     }
    900 }
    901 impl Ed25519PubKey<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]> {
    902     /// Validates `self` is in fact a valid Ed25519 public key.
    903     ///
    904     /// # Errors
    905     ///
    906     /// Errors iff `self` is not a valid Ed25519 public key.
    907     #[inline]
    908     pub fn validate(self) -> Result<(), PubKeyErr> {
    909         self.into_ver_key().map(|_| ())
    910     }
    911     /// Converts `self` into [`VerifyingKey`].
    912     fn into_ver_key(self) -> Result<VerifyingKey, PubKeyErr> {
    913         // ["Taming the many EdDSAs"](https://eprint.iacr.org/2020/1244.pdf) goes over
    914         // and proves varying levels of signature security. The only property that is
    915         // important for WebAuthn is existential unforgeability under chosen message
    916         // attacks (EUF-CMA). No matter how `ed25519-dalek` is used this is met.
    917         // Additional properties that may be of importance are strong unforgeability
    918         // under chosen message attacks (SUF-CMA), binding signature (BS), and
    919         // strongly binding signature (SBS).
    920         // No matter how `ed25519-dalek` is used, SUF-CMA is achieved. Because
    921         // we always achieve SUF-CMA, we elect—despite no benefit in WebAuthn—to also
    922         // achieve SBS. One can achieve SBS-secure by simply rejecting small-order
    923         // keys which is precisely what `VerifyingKey::is_weak` does.
    924         // Note this means we _don't_ conform to [RFC 8032](https://www.rfc-editor.org/rfc/rfc8032)
    925         // nor [NIST SP 800-186](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-186.pdf).
    926         // As stated, there is no additional security by conforming to the above specs though;
    927         // furthermore, RFC 8032 does not require rejecting small-order points despite requiring
    928         // canonical encoding of points; thus it does not achieve SBS-security.
    929         // NIST SP 800-186 does achieve SUF-CMA and SBS-security but requires additional properties
    930         // that have no cryptographic importance. Specifically it mandates canonicity of encodings
    931         // for both public keys and signatures and requires not only that points not be small-order
    932         // but more strictly that points are in the prime-order subgroup (excluding the identity).
    933         // This is more work for no benefit, so we elect to stay within the confines of the exposed API.
    934         VerifyingKey::from_bytes(&self.0)
    935             .map_err(|_e| PubKeyErr::Ed25519)
    936             .and_then(|key| {
    937                 if key.is_weak() {
    938                     Err(PubKeyErr::Ed25519)
    939                 } else {
    940                     Ok(key)
    941                 }
    942             })
    943     }
    944 }
    945 impl<'a: 'b, 'b> TryFrom<&'a [u8]> for Ed25519PubKey<&'b [u8]> {
    946     type Error = Ed25519PubKeyErr;
    947     /// Interprets `value` as the compressed y-coordinate of an Ed25519 public key.
    948     #[inline]
    949     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
    950         if value.len() == ed25519_dalek::PUBLIC_KEY_LENGTH {
    951             Ok(Self(value))
    952         } else {
    953             Err(Ed25519PubKeyErr)
    954         }
    955     }
    956 }
    957 impl From<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>
    958     for Ed25519PubKey<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>
    959 {
    960     #[inline]
    961     fn from(value: [u8; ed25519_dalek::PUBLIC_KEY_LENGTH]) -> Self {
    962         Self(value)
    963     }
    964 }
    965 impl<'a: 'b, 'b> From<&'a Ed25519PubKey<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>>
    966     for Ed25519PubKey<&'b [u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>
    967 {
    968     #[inline]
    969     fn from(value: &'a Ed25519PubKey<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>) -> Self {
    970         Self(&value.0)
    971     }
    972 }
    973 impl<'a: 'b, 'b> From<Ed25519PubKey<&'a [u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>>
    974     for Ed25519PubKey<&'b [u8]>
    975 {
    976     #[inline]
    977     fn from(value: Ed25519PubKey<&'a [u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>) -> Self {
    978         Self(value.0.as_slice())
    979     }
    980 }
    981 impl<'a: 'b, 'b> From<&'a Ed25519PubKey<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>>
    982     for Ed25519PubKey<&'b [u8]>
    983 {
    984     #[inline]
    985     fn from(value: &'a Ed25519PubKey<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>) -> Self {
    986         Self(value.0.as_slice())
    987     }
    988 }
    989 impl<'a: 'b, 'b> From<Ed25519PubKey<&'a [u8]>>
    990     for Ed25519PubKey<&'b [u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>
    991 {
    992     #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")]
    993     #[inline]
    994     fn from(value: Ed25519PubKey<&'a [u8]>) -> Self {
    995         Self(
    996             value
    997                 .0
    998                 .as_array()
    999                 .unwrap_or_else(|| unreachable!("there is a bug in slice::as_array")),
   1000         )
   1001     }
   1002 }
   1003 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<Ed25519PubKey<T>> for Ed25519PubKey<T2> {
   1004     #[inline]
   1005     fn eq(&self, other: &Ed25519PubKey<T>) -> bool {
   1006         self.0 == other.0
   1007     }
   1008 }
   1009 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<Ed25519PubKey<T>> for &Ed25519PubKey<T2> {
   1010     #[inline]
   1011     fn eq(&self, other: &Ed25519PubKey<T>) -> bool {
   1012         **self == *other
   1013     }
   1014 }
   1015 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<&Ed25519PubKey<T>> for Ed25519PubKey<T2> {
   1016     #[inline]
   1017     fn eq(&self, other: &&Ed25519PubKey<T>) -> bool {
   1018         *self == **other
   1019     }
   1020 }
   1021 impl<T: Eq> Eq for Ed25519PubKey<T> {}
   1022 /// Two 32-byte regions representing the big-endian x and y coordinates of an alleged P-256 public key.
   1023 #[derive(Clone, Copy, Debug)]
   1024 pub struct UncompressedP256PubKey<'a>(&'a [u8], &'a [u8]);
   1025 impl<'a> UncompressedP256PubKey<'a> {
   1026     /// Returns the big-endian x-coordinate.
   1027     #[inline]
   1028     #[must_use]
   1029     pub const fn x(self) -> &'a [u8] {
   1030         self.0
   1031     }
   1032     /// Returns the big-endian y-coordinate.
   1033     #[inline]
   1034     #[must_use]
   1035     pub const fn y(self) -> &'a [u8] {
   1036         self.1
   1037     }
   1038     /// Validates `self` is in fact a valid P-256 public key.
   1039     ///
   1040     /// # Errors
   1041     ///
   1042     /// Errors iff `self` is not a valid P-256 public key.
   1043     #[inline]
   1044     pub fn validate(self) -> Result<(), PubKeyErr> {
   1045         self.into_ver_key().map(|_| ())
   1046     }
   1047     /// Converts `self` into [`P256VerKey`].
   1048     #[expect(clippy::unreachable, reason = "want to crash when there is a bug")]
   1049     fn into_ver_key(self) -> Result<P256VerKey, PubKeyErr> {
   1050         P256VerKey::from_sec1_point(&P256Pt::from_affine_coordinates(
   1051             self.0
   1052                 .as_array()
   1053                 .unwrap_or_else(|| unreachable!("there is a bug in slice::as_array"))
   1054                 .into(),
   1055             self.1
   1056                 .as_array()
   1057                 .unwrap_or_else(|| unreachable!("there is a bug in slice::as_array"))
   1058                 .into(),
   1059             false,
   1060         ))
   1061         .map_err(|_e| PubKeyErr::P256)
   1062     }
   1063     /// Returns `true` iff [`Self::y`] is odd.
   1064     #[expect(clippy::indexing_slicing, reason = "comment justifies correctness")]
   1065     #[inline]
   1066     #[must_use]
   1067     pub const fn y_is_odd(self) -> bool {
   1068         // `self.1.len() == 32`, so this won't `panic`.
   1069         self.1[31] & 1 == 1
   1070     }
   1071     /// Transforms `self` into the compressed version that owns the data.
   1072     #[expect(clippy::unreachable, reason = "want to crash when there is a bug")]
   1073     #[inline]
   1074     #[must_use]
   1075     pub fn into_compressed(
   1076         self,
   1077     ) -> CompressedP256PubKey<[u8; <NistP256 as Curve>::FieldBytesSize::INT]> {
   1078         CompressedP256PubKey {
   1079             x: self.0.try_into().unwrap_or_else(|_e| unreachable!("there is a bug in UncompressedP256PubKey that allows for the x-coordinate to not be 32 bytes in length")),
   1080             y_is_odd: self.y_is_odd(),
   1081         }
   1082     }
   1083 }
   1084 impl<'a: 'b, 'b> TryFrom<(&'a [u8], &'a [u8])> for UncompressedP256PubKey<'b> {
   1085     type Error = UncompressedP256PubKeyErr;
   1086     /// The first item is the big-endian x-coordinate, and the second item is the big-endian y-coordinate.
   1087     #[inline]
   1088     fn try_from((x, y): (&'a [u8], &'a [u8])) -> Result<Self, Self::Error> {
   1089         /// Number of bytes each coordinate is made of.
   1090         const COORD_LEN: usize = <NistP256 as Curve>::FieldBytesSize::INT;
   1091         if x.len() == COORD_LEN {
   1092             if y.len() == COORD_LEN {
   1093                 Ok(Self(x, y))
   1094             } else {
   1095                 Err(UncompressedP256PubKeyErr::Y)
   1096             }
   1097         } else {
   1098             Err(UncompressedP256PubKeyErr::X)
   1099         }
   1100     }
   1101 }
   1102 impl PartialEq<UncompressedP256PubKey<'_>> for UncompressedP256PubKey<'_> {
   1103     #[inline]
   1104     fn eq(&self, other: &UncompressedP256PubKey<'_>) -> bool {
   1105         self.0 == other.0 && self.1 == other.1
   1106     }
   1107 }
   1108 impl PartialEq<UncompressedP256PubKey<'_>> for &UncompressedP256PubKey<'_> {
   1109     #[inline]
   1110     fn eq(&self, other: &UncompressedP256PubKey<'_>) -> bool {
   1111         **self == *other
   1112     }
   1113 }
   1114 impl PartialEq<&UncompressedP256PubKey<'_>> for UncompressedP256PubKey<'_> {
   1115     #[inline]
   1116     fn eq(&self, other: &&UncompressedP256PubKey<'_>) -> bool {
   1117         *self == **other
   1118     }
   1119 }
   1120 impl Eq for UncompressedP256PubKey<'_> {}
   1121 /// 32-bytes representing the big-endian x-coordinate and a `bool` representing whether the y-coordinate
   1122 /// is odd of an alleged P-256 public key.
   1123 #[derive(Clone, Copy, Debug)]
   1124 pub struct CompressedP256PubKey<T> {
   1125     /// 32-byte x-coordinate.
   1126     x: T,
   1127     /// `true` iff the y-coordinate is odd.
   1128     y_is_odd: bool,
   1129 }
   1130 impl<T> CompressedP256PubKey<T> {
   1131     /// Returns [`Self::x`] and [`Self::y_is_odd`] consuming `self`.
   1132     #[inline]
   1133     pub fn into_parts(self) -> (T, bool) {
   1134         (self.x, self.y_is_odd)
   1135     }
   1136     /// Returns [`Self::x`] and [`Self::y_is_odd`].
   1137     #[inline]
   1138     pub const fn as_parts(&self) -> (&T, bool) {
   1139         (&self.x, self.y_is_odd)
   1140     }
   1141     /// Returns the 32-byte big-endian x-coordinate.
   1142     #[inline]
   1143     pub const fn x(&self) -> &T {
   1144         &self.x
   1145     }
   1146     /// `true` iff the y-coordinate is odd.
   1147     #[inline]
   1148     pub const fn y_is_odd(&self) -> bool {
   1149         self.y_is_odd
   1150     }
   1151 }
   1152 impl CompressedP256PubKey<[u8; <NistP256 as Curve>::FieldBytesSize::INT]> {
   1153     /// Validates `self` is in fact a valid P-256 public key.
   1154     ///
   1155     /// # Errors
   1156     ///
   1157     /// Errors iff `self` is not a valid P-256 public key.
   1158     #[inline]
   1159     pub fn validate(self) -> Result<(), PubKeyErr> {
   1160         self.into_ver_key().map(|_| ())
   1161     }
   1162     /// Converts `self` into [`P256VerKey`].
   1163     pub(super) fn into_ver_key(self) -> Result<P256VerKey, PubKeyErr> {
   1164         P256Affine::decompress(&self.x.into(), u8::from(self.y_is_odd).into())
   1165             .into_option()
   1166             .ok_or(PubKeyErr::P256)
   1167             .and_then(|pt| P256VerKey::from_affine(pt).map_err(|_e| PubKeyErr::P256))
   1168     }
   1169 }
   1170 impl CompressedP256PubKey<&[u8]> {
   1171     /// Validates `self` is in fact a valid P-256 public key.
   1172     ///
   1173     /// # Errors
   1174     ///
   1175     /// Errors iff `self` is not a valid P-256 public key.
   1176     #[inline]
   1177     pub fn validate(self) -> Result<(), PubKeyErr> {
   1178         self.into_ver_key().map(|_| ())
   1179     }
   1180     /// Converts `self` into [`P256VerKey`].
   1181     #[expect(clippy::unreachable, reason = "want to crash when there is a bug")]
   1182     pub(super) fn into_ver_key(self) -> Result<P256VerKey, PubKeyErr> {
   1183         P256Affine::decompress(
   1184             self.x
   1185                 .as_array()
   1186                 .unwrap_or_else(|| unreachable!("there is a bug in slice::as_array"))
   1187                 .into(),
   1188             u8::from(self.y_is_odd).into(),
   1189         )
   1190         .into_option()
   1191         .ok_or(PubKeyErr::P256)
   1192         .and_then(|pt| P256VerKey::from_affine(pt).map_err(|_e| PubKeyErr::P256))
   1193     }
   1194 }
   1195 impl<'a: 'b, 'b> TryFrom<(&'a [u8], bool)> for CompressedP256PubKey<&'b [u8]> {
   1196     type Error = CompressedP256PubKeyErr;
   1197     #[inline]
   1198     fn try_from((x, y_is_odd): (&'a [u8], bool)) -> Result<Self, Self::Error> {
   1199         /// The number of bytes the x-coordinate is.
   1200         const X_LEN: usize = <NistP256 as Curve>::FieldBytesSize::INT;
   1201         if x.len() == X_LEN {
   1202             Ok(Self { x, y_is_odd })
   1203         } else {
   1204             Err(CompressedP256PubKeyErr)
   1205         }
   1206     }
   1207 }
   1208 impl From<([u8; <NistP256 as Curve>::FieldBytesSize::INT], bool)>
   1209     for CompressedP256PubKey<[u8; <NistP256 as Curve>::FieldBytesSize::INT]>
   1210 {
   1211     #[inline]
   1212     fn from((x, y_is_odd): ([u8; <NistP256 as Curve>::FieldBytesSize::INT], bool)) -> Self {
   1213         Self { x, y_is_odd }
   1214     }
   1215 }
   1216 impl<'a: 'b, 'b> From<&'a CompressedP256PubKey<[u8; <NistP256 as Curve>::FieldBytesSize::INT]>>
   1217     for CompressedP256PubKey<&'b [u8; <NistP256 as Curve>::FieldBytesSize::INT]>
   1218 {
   1219     #[inline]
   1220     fn from(
   1221         value: &'a CompressedP256PubKey<[u8; <NistP256 as Curve>::FieldBytesSize::INT]>,
   1222     ) -> Self {
   1223         Self {
   1224             x: &value.x,
   1225             y_is_odd: value.y_is_odd,
   1226         }
   1227     }
   1228 }
   1229 impl<'a: 'b, 'b> From<CompressedP256PubKey<&'a [u8; <NistP256 as Curve>::FieldBytesSize::INT]>>
   1230     for CompressedP256PubKey<&'b [u8]>
   1231 {
   1232     #[inline]
   1233     fn from(
   1234         value: CompressedP256PubKey<&'a [u8; <NistP256 as Curve>::FieldBytesSize::INT]>,
   1235     ) -> Self {
   1236         Self {
   1237             x: value.x.as_slice(),
   1238             y_is_odd: value.y_is_odd,
   1239         }
   1240     }
   1241 }
   1242 impl<'a: 'b, 'b> From<&'a CompressedP256PubKey<[u8; <NistP256 as Curve>::FieldBytesSize::INT]>>
   1243     for CompressedP256PubKey<&'b [u8]>
   1244 {
   1245     #[inline]
   1246     fn from(
   1247         value: &'a CompressedP256PubKey<[u8; <NistP256 as Curve>::FieldBytesSize::INT]>,
   1248     ) -> Self {
   1249         Self {
   1250             x: value.x.as_slice(),
   1251             y_is_odd: value.y_is_odd,
   1252         }
   1253     }
   1254 }
   1255 impl<'a: 'b, 'b> From<CompressedP256PubKey<&'a [u8]>>
   1256     for CompressedP256PubKey<&'b [u8; <NistP256 as Curve>::FieldBytesSize::INT]>
   1257 {
   1258     #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")]
   1259     #[inline]
   1260     fn from(value: CompressedP256PubKey<&'a [u8]>) -> Self {
   1261         Self {
   1262             x: value
   1263                 .x
   1264                 .as_array()
   1265                 .unwrap_or_else(|| unreachable!("there is a bug in slice::as_array")),
   1266             y_is_odd: value.y_is_odd,
   1267         }
   1268     }
   1269 }
   1270 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<CompressedP256PubKey<T>>
   1271     for CompressedP256PubKey<T2>
   1272 {
   1273     #[inline]
   1274     fn eq(&self, other: &CompressedP256PubKey<T>) -> bool {
   1275         self.x == other.x && self.y_is_odd == other.y_is_odd
   1276     }
   1277 }
   1278 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<CompressedP256PubKey<T>>
   1279     for &CompressedP256PubKey<T2>
   1280 {
   1281     #[inline]
   1282     fn eq(&self, other: &CompressedP256PubKey<T>) -> bool {
   1283         **self == *other
   1284     }
   1285 }
   1286 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<&CompressedP256PubKey<T>>
   1287     for CompressedP256PubKey<T2>
   1288 {
   1289     #[inline]
   1290     fn eq(&self, other: &&CompressedP256PubKey<T>) -> bool {
   1291         *self == **other
   1292     }
   1293 }
   1294 impl<T: Eq> Eq for CompressedP256PubKey<T> {}
   1295 /// Two 48-byte regions representing the big-endian x and y coordinates of an alleged P-384 public key.
   1296 #[derive(Clone, Copy, Debug)]
   1297 pub struct UncompressedP384PubKey<'a>(&'a [u8], &'a [u8]);
   1298 impl<'a> UncompressedP384PubKey<'a> {
   1299     /// Returns the big-endian x-coordinate.
   1300     #[inline]
   1301     #[must_use]
   1302     pub const fn x(self) -> &'a [u8] {
   1303         self.0
   1304     }
   1305     /// Returns the big-endian y-coordinate.
   1306     #[inline]
   1307     #[must_use]
   1308     pub const fn y(self) -> &'a [u8] {
   1309         self.1
   1310     }
   1311     /// Validates `self` is in fact a valid P-384 public key.
   1312     ///
   1313     /// # Errors
   1314     ///
   1315     /// Errors iff `self` is not a valid P-384 public key.
   1316     #[inline]
   1317     pub fn validate(self) -> Result<(), PubKeyErr> {
   1318         self.into_ver_key().map(|_| ())
   1319     }
   1320     /// Converts `self` into [`P384VerKey`].
   1321     #[expect(clippy::unreachable, reason = "want to crash when there is a bug")]
   1322     fn into_ver_key(self) -> Result<P384VerKey, PubKeyErr> {
   1323         P384VerKey::from_sec1_point(&P384Pt::from_affine_coordinates(
   1324             self.0
   1325                 .as_array()
   1326                 .unwrap_or_else(|| unreachable!("there is a bug in slice::as_array"))
   1327                 .into(),
   1328             self.1
   1329                 .as_array()
   1330                 .unwrap_or_else(|| unreachable!("there is a bug in slice::as_array"))
   1331                 .into(),
   1332             false,
   1333         ))
   1334         .map_err(|_e| PubKeyErr::P384)
   1335     }
   1336     /// Returns `true` iff [`Self::y`] is odd.
   1337     #[expect(clippy::indexing_slicing, reason = "comment justifies correctness")]
   1338     #[inline]
   1339     #[must_use]
   1340     pub const fn y_is_odd(self) -> bool {
   1341         // `self.1.len() == 48`, so this won't `panic`.
   1342         self.1[47] & 1 == 1
   1343     }
   1344     /// Transforms `self` into the compressed version that owns the data.
   1345     #[expect(clippy::unreachable, reason = "want to crash when there is a bug")]
   1346     #[inline]
   1347     #[must_use]
   1348     pub fn into_compressed(
   1349         self,
   1350     ) -> CompressedP384PubKey<[u8; <NistP384 as Curve>::FieldBytesSize::INT]> {
   1351         CompressedP384PubKey {
   1352             x: self.0.try_into().unwrap_or_else(|_e| unreachable!("there is a bug in UncompressedP384PubKey that allows for the x-coordinate to not be 48 bytes in length")),
   1353             y_is_odd: self.y_is_odd(),
   1354         }
   1355     }
   1356 }
   1357 impl<'a: 'b, 'b> TryFrom<(&'a [u8], &'a [u8])> for UncompressedP384PubKey<'b> {
   1358     type Error = UncompressedP384PubKeyErr;
   1359     /// The first item is the big-endian x-coordinate, and the second item is the big-endian y-coordinate.
   1360     #[inline]
   1361     fn try_from((x, y): (&'a [u8], &'a [u8])) -> Result<Self, Self::Error> {
   1362         /// Number of bytes each coordinate is made of.
   1363         const COORD_LEN: usize = <NistP384 as Curve>::FieldBytesSize::INT;
   1364         if x.len() == COORD_LEN {
   1365             if y.len() == COORD_LEN {
   1366                 Ok(Self(x, y))
   1367             } else {
   1368                 Err(UncompressedP384PubKeyErr::Y)
   1369             }
   1370         } else {
   1371             Err(UncompressedP384PubKeyErr::X)
   1372         }
   1373     }
   1374 }
   1375 impl PartialEq<UncompressedP384PubKey<'_>> for UncompressedP384PubKey<'_> {
   1376     #[inline]
   1377     fn eq(&self, other: &UncompressedP384PubKey<'_>) -> bool {
   1378         self.0 == other.0 && self.1 == other.1
   1379     }
   1380 }
   1381 impl PartialEq<UncompressedP384PubKey<'_>> for &UncompressedP384PubKey<'_> {
   1382     #[inline]
   1383     fn eq(&self, other: &UncompressedP384PubKey<'_>) -> bool {
   1384         **self == *other
   1385     }
   1386 }
   1387 impl PartialEq<&UncompressedP384PubKey<'_>> for UncompressedP384PubKey<'_> {
   1388     #[inline]
   1389     fn eq(&self, other: &&UncompressedP384PubKey<'_>) -> bool {
   1390         *self == **other
   1391     }
   1392 }
   1393 impl Eq for UncompressedP384PubKey<'_> {}
   1394 /// 48-bytes representing the big-endian x-coordinate and a `bool` representing whether the y-coordinate
   1395 /// is odd of an alleged P-384 public key.
   1396 #[derive(Clone, Copy, Debug)]
   1397 pub struct CompressedP384PubKey<T> {
   1398     /// 48-byte x-coordinate.
   1399     x: T,
   1400     /// `true` iff the y-coordinate is odd.
   1401     y_is_odd: bool,
   1402 }
   1403 impl<T> CompressedP384PubKey<T> {
   1404     /// Returns [`Self::x`] and [`Self::y_is_odd`] consuming `self`.
   1405     #[inline]
   1406     pub fn into_parts(self) -> (T, bool) {
   1407         (self.x, self.y_is_odd)
   1408     }
   1409     /// Returns [`Self::x`] and [`Self::y_is_odd`].
   1410     #[inline]
   1411     pub const fn as_parts(&self) -> (&T, bool) {
   1412         (&self.x, self.y_is_odd)
   1413     }
   1414     /// Returns the 48-byte big-endian x-coordinate.
   1415     #[inline]
   1416     pub const fn x(&self) -> &T {
   1417         &self.x
   1418     }
   1419     /// `true` iff the y-coordinate is odd.
   1420     #[inline]
   1421     #[must_use]
   1422     pub const fn y_is_odd(&self) -> bool {
   1423         self.y_is_odd
   1424     }
   1425 }
   1426 impl CompressedP384PubKey<[u8; <NistP384 as Curve>::FieldBytesSize::INT]> {
   1427     /// Validates `self` is in fact a valid P-384 public key.
   1428     ///
   1429     /// # Errors
   1430     ///
   1431     /// Errors iff `self` is not a valid P-384 public key.
   1432     #[inline]
   1433     pub fn validate(self) -> Result<(), PubKeyErr> {
   1434         self.into_ver_key().map(|_| ())
   1435     }
   1436     /// Converts `self` into [`P384VerKey`].
   1437     pub(super) fn into_ver_key(self) -> Result<P384VerKey, PubKeyErr> {
   1438         P384Affine::decompress(&self.x.into(), u8::from(self.y_is_odd).into())
   1439             .into_option()
   1440             .ok_or(PubKeyErr::P384)
   1441             .and_then(|pt| P384VerKey::from_affine(pt).map_err(|_e| PubKeyErr::P384))
   1442     }
   1443 }
   1444 impl CompressedP384PubKey<&[u8]> {
   1445     /// Validates `self` is in fact a valid P-384 public key.
   1446     ///
   1447     /// # Errors
   1448     ///
   1449     /// Errors iff `self` is not a valid P-384 public key.
   1450     #[inline]
   1451     pub fn validate(self) -> Result<(), PubKeyErr> {
   1452         self.into_ver_key().map(|_| ())
   1453     }
   1454     /// Converts `self` into [`P384VerKey`].
   1455     #[expect(clippy::unreachable, reason = "want to crash when there is a bug")]
   1456     pub(super) fn into_ver_key(self) -> Result<P384VerKey, PubKeyErr> {
   1457         P384Affine::decompress(
   1458             self.x
   1459                 .as_array()
   1460                 .unwrap_or_else(|| unreachable!("there is a bug in slice::as_array"))
   1461                 .into(),
   1462             u8::from(self.y_is_odd).into(),
   1463         )
   1464         .into_option()
   1465         .ok_or(PubKeyErr::P384)
   1466         .and_then(|pt| P384VerKey::from_affine(pt).map_err(|_e| PubKeyErr::P384))
   1467     }
   1468 }
   1469 impl<'a: 'b, 'b> TryFrom<(&'a [u8], bool)> for CompressedP384PubKey<&'b [u8]> {
   1470     type Error = CompressedP384PubKeyErr;
   1471     #[inline]
   1472     fn try_from((x, y_is_odd): (&'a [u8], bool)) -> Result<Self, Self::Error> {
   1473         /// Number of bytes of the x-coordinate is.
   1474         const X_LEN: usize = <NistP384 as Curve>::FieldBytesSize::INT;
   1475         if x.len() == X_LEN {
   1476             Ok(Self { x, y_is_odd })
   1477         } else {
   1478             Err(CompressedP384PubKeyErr)
   1479         }
   1480     }
   1481 }
   1482 impl From<([u8; <NistP384 as Curve>::FieldBytesSize::INT], bool)>
   1483     for CompressedP384PubKey<[u8; <NistP384 as Curve>::FieldBytesSize::INT]>
   1484 {
   1485     #[inline]
   1486     fn from((x, y_is_odd): ([u8; <NistP384 as Curve>::FieldBytesSize::INT], bool)) -> Self {
   1487         Self { x, y_is_odd }
   1488     }
   1489 }
   1490 impl<'a: 'b, 'b> From<&'a CompressedP384PubKey<[u8; <NistP384 as Curve>::FieldBytesSize::INT]>>
   1491     for CompressedP384PubKey<&'b [u8; <NistP384 as Curve>::FieldBytesSize::INT]>
   1492 {
   1493     #[inline]
   1494     fn from(
   1495         value: &'a CompressedP384PubKey<[u8; <NistP384 as Curve>::FieldBytesSize::INT]>,
   1496     ) -> Self {
   1497         Self {
   1498             x: &value.x,
   1499             y_is_odd: value.y_is_odd,
   1500         }
   1501     }
   1502 }
   1503 impl<'a: 'b, 'b> From<CompressedP384PubKey<&'a [u8; <NistP384 as Curve>::FieldBytesSize::INT]>>
   1504     for CompressedP384PubKey<&'b [u8]>
   1505 {
   1506     #[inline]
   1507     fn from(
   1508         value: CompressedP384PubKey<&'a [u8; <NistP384 as Curve>::FieldBytesSize::INT]>,
   1509     ) -> Self {
   1510         Self {
   1511             x: value.x.as_slice(),
   1512             y_is_odd: value.y_is_odd,
   1513         }
   1514     }
   1515 }
   1516 impl<'a: 'b, 'b> From<&'a CompressedP384PubKey<[u8; <NistP384 as Curve>::FieldBytesSize::INT]>>
   1517     for CompressedP384PubKey<&'b [u8]>
   1518 {
   1519     #[inline]
   1520     fn from(
   1521         value: &'a CompressedP384PubKey<[u8; <NistP384 as Curve>::FieldBytesSize::INT]>,
   1522     ) -> Self {
   1523         Self {
   1524             x: value.x.as_slice(),
   1525             y_is_odd: value.y_is_odd,
   1526         }
   1527     }
   1528 }
   1529 impl<'a: 'b, 'b> From<CompressedP384PubKey<&'a [u8]>>
   1530     for CompressedP384PubKey<&'b [u8; <NistP384 as Curve>::FieldBytesSize::INT]>
   1531 {
   1532     #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")]
   1533     #[inline]
   1534     fn from(value: CompressedP384PubKey<&'a [u8]>) -> Self {
   1535         Self {
   1536             x: value
   1537                 .x
   1538                 .as_array()
   1539                 .unwrap_or_else(|| unreachable!("there is a bug in slice::as_array")),
   1540             y_is_odd: value.y_is_odd,
   1541         }
   1542     }
   1543 }
   1544 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<CompressedP384PubKey<T>>
   1545     for CompressedP384PubKey<T2>
   1546 {
   1547     #[inline]
   1548     fn eq(&self, other: &CompressedP384PubKey<T>) -> bool {
   1549         self.x == other.x && self.y_is_odd == other.y_is_odd
   1550     }
   1551 }
   1552 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<CompressedP384PubKey<T>>
   1553     for &CompressedP384PubKey<T2>
   1554 {
   1555     #[inline]
   1556     fn eq(&self, other: &CompressedP384PubKey<T>) -> bool {
   1557         **self == *other
   1558     }
   1559 }
   1560 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<&CompressedP384PubKey<T>>
   1561     for CompressedP384PubKey<T2>
   1562 {
   1563     #[inline]
   1564     fn eq(&self, other: &&CompressedP384PubKey<T>) -> bool {
   1565         *self == **other
   1566     }
   1567 }
   1568 impl<T: Eq> Eq for CompressedP384PubKey<T> {}
   1569 /// The minimum RSA public key exponent allowed by [`RsaPubKey`].
   1570 ///
   1571 /// [RFC 8017 § 3.1](https://www.rfc-editor.org/rfc/rfc8017#section-3.1) states the smallest valid RSA public
   1572 /// exponent is 3.
   1573 pub const MIN_RSA_E: u32 = 3;
   1574 /// The most bits an RSA public key modulus is allowed to consist of per [`RsaPubKey`].
   1575 ///
   1576 /// [RFC 8230 § 6.1](https://www.rfc-editor.org/rfc/rfc8230#section-6.1) recommends allowing moduli up to 16K bits.
   1577 pub const MAX_RSA_N_BITS: usize = 0x4000;
   1578 /// The fewest bits an RSA public key modulus is allowed to consist of per [`RsaPubKey`].
   1579 ///
   1580 /// [RFC 8230 § 6.1](https://www.rfc-editor.org/rfc/rfc8230#section-6.1) requires the modulus to be at least 2048
   1581 /// bits.
   1582 pub const MIN_RSA_N_BITS: usize = 0x800;
   1583 /// [`MIN_RSA_N_BITS`]–[`MAX_RSA_N_BITS`] bits representing the big-endian modulus and a `u32` `>=`
   1584 /// [`MIN_RSA_E`] representing the exponent of an alleged RSA public key.
   1585 ///
   1586 /// Note the modulus and exponent are always odd.
   1587 #[derive(Clone, Copy, Debug)]
   1588 pub struct RsaPubKey<T>(T, u32);
   1589 impl<T> RsaPubKey<T> {
   1590     /// Returns [`Self::n`] and [`Self::e`] consuming `self`.
   1591     #[inline]
   1592     pub fn into_parts(self) -> (T, u32) {
   1593         (self.0, self.1)
   1594     }
   1595     /// Returns [`Self::n`] and [`Self::e`].
   1596     #[inline]
   1597     pub const fn as_parts(&self) -> (&T, u32) {
   1598         (&self.0, self.1)
   1599     }
   1600     /// Returns the big-endian modulus.
   1601     #[inline]
   1602     pub const fn n(&self) -> &T {
   1603         &self.0
   1604     }
   1605     /// Returns the exponent.
   1606     #[inline]
   1607     pub const fn e(&self) -> u32 {
   1608         self.1
   1609     }
   1610 }
   1611 impl<T: AsRef<[u8]>> RsaPubKey<T> {
   1612     /// Converts `self` into [`RsaVerKey`].
   1613     pub(super) fn as_ver_key(&self) -> RsaVerKey<Sha256> {
   1614         RsaVerKey::new(RsaPublicKey::new_unchecked(
   1615             BoxedUint::from_be_slice_vartime(self.0.as_ref()),
   1616             self.1.into(),
   1617         ))
   1618     }
   1619 }
   1620 impl RsaPubKey<&[u8]> {
   1621     /// Transforms `self` into an "owned" version.
   1622     #[inline]
   1623     #[must_use]
   1624     pub fn into_owned(self) -> RsaPubKey<Box<[u8]>> {
   1625         RsaPubKey(self.0.into(), self.1)
   1626     }
   1627 }
   1628 impl<'a: 'b, 'b> TryFrom<(&'a [u8], u32)> for RsaPubKey<&'b [u8]> {
   1629     type Error = RsaPubKeyErr;
   1630     /// The first item is the big-endian modulus, and the second item is the exponent.
   1631     #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")]
   1632     #[expect(
   1633         clippy::arithmetic_side_effects,
   1634         clippy::as_conversions,
   1635         reason = "comment justifies correctness"
   1636     )]
   1637     #[inline]
   1638     fn try_from((n, e): (&'a [u8], u32)) -> Result<Self, Self::Error> {
   1639         n.first().map_or(Err(RsaPubKeyErr::NSize), |fst| {
   1640             // `fst.leading_zeros()` is inclusively between `0` and `8`, so it's safe to convert to a
   1641             // `usize`.
   1642             let zeros = fst.leading_zeros() as usize;
   1643             if zeros == 8 {
   1644                 Err(RsaPubKeyErr::NLeading0)
   1645                 // `bits` is at least 8 since `n.len()` is at least 1; thus underflow cannot occur.
   1646             } else if let Some(bits) = n.len().checked_mul(8)
   1647                 && (MIN_RSA_N_BITS..=MAX_RSA_N_BITS).contains(&(bits - zeros))
   1648             {
   1649                 // We know `n` is not empty, so this won't `panic`.
   1650                 if n.last()
   1651                     .unwrap_or_else(|| unreachable!("there is a bug in RsaPubKey::try_from"))
   1652                     & 1
   1653                     == 0
   1654                 {
   1655                     Err(RsaPubKeyErr::NEven)
   1656                 } else if e < MIN_RSA_E {
   1657                     Err(RsaPubKeyErr::ESize)
   1658                 } else if e & 1 == 0 {
   1659                     Err(RsaPubKeyErr::EEven)
   1660                 } else {
   1661                     Ok(Self(n, e))
   1662                 }
   1663             } else {
   1664                 Err(RsaPubKeyErr::NSize)
   1665             }
   1666         })
   1667     }
   1668 }
   1669 impl TryFrom<(Box<[u8]>, u32)> for RsaPubKey<Box<[u8]>> {
   1670     type Error = RsaPubKeyErr;
   1671     /// Similar to [`RsaPubKey::try_from`] except `n` is a `Box`.
   1672     #[inline]
   1673     fn try_from((n, e): (Box<[u8]>, u32)) -> Result<Self, Self::Error> {
   1674         match RsaPubKey::<&[u8]>::try_from((&*n, e)) {
   1675             Ok(_) => Ok(Self(n, e)),
   1676             Err(err) => Err(err),
   1677         }
   1678     }
   1679 }
   1680 impl<'a: 'b, 'b> From<&'a RsaPubKey<Box<[u8]>>> for RsaPubKey<&'b Box<[u8]>> {
   1681     #[inline]
   1682     fn from(value: &'a RsaPubKey<Box<[u8]>>) -> Self {
   1683         Self(&value.0, value.1)
   1684     }
   1685 }
   1686 impl<'a: 'b, 'b> From<RsaPubKey<&'a Box<[u8]>>> for RsaPubKey<&'b [u8]> {
   1687     #[inline]
   1688     fn from(value: RsaPubKey<&'a Box<[u8]>>) -> Self {
   1689         Self(value.0, value.1)
   1690     }
   1691 }
   1692 impl<'a: 'b, 'b> From<&'a RsaPubKey<Box<[u8]>>> for RsaPubKey<&'b [u8]> {
   1693     #[inline]
   1694     fn from(value: &'a RsaPubKey<Box<[u8]>>) -> Self {
   1695         Self(&value.0, value.1)
   1696     }
   1697 }
   1698 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<RsaPubKey<T>> for RsaPubKey<T2> {
   1699     #[inline]
   1700     fn eq(&self, other: &RsaPubKey<T>) -> bool {
   1701         self.0 == other.0 && self.1 == other.1
   1702     }
   1703 }
   1704 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<RsaPubKey<T>> for &RsaPubKey<T2> {
   1705     #[inline]
   1706     fn eq(&self, other: &RsaPubKey<T>) -> bool {
   1707         **self == *other
   1708     }
   1709 }
   1710 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<&RsaPubKey<T>> for RsaPubKey<T2> {
   1711     #[inline]
   1712     fn eq(&self, other: &&RsaPubKey<T>) -> bool {
   1713         *self == **other
   1714     }
   1715 }
   1716 impl<T: Eq> Eq for RsaPubKey<T> {}
   1717 /// `kty` COSE key common parameter as defined by
   1718 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-common-parameters).
   1719 const KTY: u8 = cbor::ONE;
   1720 /// `OKP` COSE key type as defined by
   1721 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type).
   1722 const OKP: u8 = cbor::ONE;
   1723 /// `EC2` COSE key type as defined by
   1724 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type).
   1725 const EC2: u8 = cbor::TWO;
   1726 /// `RSA` COSE key type as defined by
   1727 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type).
   1728 const RSA: u8 = cbor::THREE;
   1729 /// `AKP` COSE key type as defined by
   1730 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type).
   1731 const AKP: u8 = cbor::SEVEN;
   1732 /// `alg` COSE key common parameter as defined by
   1733 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-common-parameters).
   1734 const ALG: u8 = cbor::THREE;
   1735 /// `EdDSA` COSE algorithm as defined by
   1736 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#algorithms).
   1737 const EDDSA: u8 = cbor::NEG_EIGHT;
   1738 /// `ES256` COSE algorithm as defined by
   1739 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#algorithms).
   1740 const ES256: u8 = cbor::NEG_SEVEN;
   1741 /// `ES384` COSE algorithm as defined by
   1742 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#algorithms).
   1743 ///
   1744 /// This is -35 encoded in cbor which is encoded as |-35| - 1 = 35 - 1 = 34. Note
   1745 /// this must be preceded with `cbor::NEG_INFO_24`.
   1746 const ES384: u8 = 34;
   1747 /// `ML-DSA-44` COSE algorithm as defined by
   1748 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#algorithms).
   1749 ///
   1750 /// This is -48 encoded in cbor which is encoded as |-48| - 1 = 48 - 1 = 47. Note
   1751 /// this must be preceded with `cbor::NEG_INFO_24`.
   1752 const MLDSA44: u8 = 47;
   1753 /// `ML-DSA-65` COSE algorithm as defined by
   1754 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#algorithms).
   1755 ///
   1756 /// This is -49 encoded in cbor which is encoded as |-49| - 1 = 49 - 1 = 48. Note
   1757 /// this must be preceded with `cbor::NEG_INFO_24`.
   1758 const MLDSA65: u8 = 48;
   1759 /// `ML-DSA-87` COSE algorithm as defined by
   1760 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#algorithms).
   1761 ///
   1762 /// This is -50 encoded in cbor which is encoded as |-50| - 1 = 50 - 1 = 49. Note
   1763 /// this must be preceded with `cbor::NEG_INFO_24`.
   1764 const MLDSA87: u8 = 49;
   1765 /// `RS256` COSE algorithm as defined by
   1766 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#algorithms).
   1767 ///
   1768 /// This is -257 encoded in cbor which is encoded as |-257| - 1 = 257 - 1 = 256 = [1, 0] in big endian.
   1769 /// Note this must be preceded with `cbor::NEG_INFO_25`.
   1770 const RS256: [u8; 2] = [1, 0];
   1771 impl<'a> FromCbor<'a> for MlDsa87PubKey<&'a [u8]> {
   1772     type Err = CoseKeyErr;
   1773     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   1774         /// `pub` COSE key type parameter for [`AKP`] as defined by
   1775         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   1776         const PUB: u8 = cbor::NEG_ONE;
   1777         /// COSE header.
   1778         /// {kty:AKP,alg:ML-DSA-87,pub:<encodedKey>}.
   1779         /// `kty` and `alg` come before `pub` since map order first
   1780         /// is done by data type and `cbor::UINT`s come before `cbor::NEG`s.
   1781         /// `kty` comes before `alg` since order is done byte-wise and
   1782         /// 1 is before 3.
   1783         const HEADER: [u8; 10] = [
   1784             cbor::MAP_3,
   1785             KTY,
   1786             AKP,
   1787             ALG,
   1788             cbor::NEG_INFO_24,
   1789             MLDSA87,
   1790             PUB,
   1791             cbor::BYTES_INFO_25,
   1792             // 10 *256 + 32 = 2592
   1793             10,
   1794             32,
   1795         ];
   1796         cbor.split_at_checked(HEADER.len())
   1797             .ok_or(CoseKeyErr::Len)
   1798             .and_then(|(header, header_rem)| {
   1799                 if header == HEADER {
   1800                     header_rem
   1801                         .split_at_checked(2592)
   1802                         .ok_or(CoseKeyErr::Len)
   1803                         .map(|(key, remaining)| CborSuccess {
   1804                             value: Self(key),
   1805                             remaining,
   1806                         })
   1807                 } else {
   1808                     Err(CoseKeyErr::MlDsa87CoseEncoding)
   1809                 }
   1810             })
   1811     }
   1812 }
   1813 impl<'a> FromCbor<'a> for MlDsa65PubKey<&'a [u8]> {
   1814     type Err = CoseKeyErr;
   1815     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   1816         /// `pub` COSE key type parameter for [`AKP`] as defined by
   1817         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   1818         const PUB: u8 = cbor::NEG_ONE;
   1819         /// COSE header.
   1820         /// {kty:AKP,alg:ML-DSA-65,pub:<encodedKey>}.
   1821         /// `kty` and `alg` come before `pub` since map order first
   1822         /// is done by data type and `cbor::UINT`s come before `cbor::NEG`s.
   1823         /// `kty` comes before `alg` since order is done byte-wise and
   1824         /// 1 is before 3.
   1825         const HEADER: [u8; 10] = [
   1826             cbor::MAP_3,
   1827             KTY,
   1828             AKP,
   1829             ALG,
   1830             cbor::NEG_INFO_24,
   1831             MLDSA65,
   1832             PUB,
   1833             cbor::BYTES_INFO_25,
   1834             // 7 *256 + 160 = 1952
   1835             7,
   1836             160,
   1837         ];
   1838         cbor.split_at_checked(HEADER.len())
   1839             .ok_or(CoseKeyErr::Len)
   1840             .and_then(|(header, header_rem)| {
   1841                 if header == HEADER {
   1842                     header_rem
   1843                         .split_at_checked(1952)
   1844                         .ok_or(CoseKeyErr::Len)
   1845                         .map(|(key, remaining)| CborSuccess {
   1846                             value: Self(key),
   1847                             remaining,
   1848                         })
   1849                 } else {
   1850                     Err(CoseKeyErr::MlDsa65CoseEncoding)
   1851                 }
   1852             })
   1853     }
   1854 }
   1855 impl<'a> FromCbor<'a> for MlDsa44PubKey<&'a [u8]> {
   1856     type Err = CoseKeyErr;
   1857     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   1858         /// `pub` COSE key type parameter for [`AKP`] as defined by
   1859         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   1860         const PUB: u8 = cbor::NEG_ONE;
   1861         /// COSE header.
   1862         /// {kty:AKP,alg:ML-DSA-44,pub:<encodedKey>}.
   1863         /// `kty` and `alg` come before `pub` since map order first
   1864         /// is done by data type and `cbor::UINT`s come before `cbor::NEG`s.
   1865         /// `kty` comes before `alg` since order is done byte-wise and
   1866         /// 1 is before 3.
   1867         const HEADER: [u8; 10] = [
   1868             cbor::MAP_3,
   1869             KTY,
   1870             AKP,
   1871             ALG,
   1872             cbor::NEG_INFO_24,
   1873             MLDSA44,
   1874             PUB,
   1875             cbor::BYTES_INFO_25,
   1876             // 5 *256 + 32 = 1312
   1877             5,
   1878             32,
   1879         ];
   1880         cbor.split_at_checked(HEADER.len())
   1881             .ok_or(CoseKeyErr::Len)
   1882             .and_then(|(header, header_rem)| {
   1883                 if header == HEADER {
   1884                     header_rem
   1885                         .split_at_checked(1312)
   1886                         .ok_or(CoseKeyErr::Len)
   1887                         .map(|(key, remaining)| CborSuccess {
   1888                             value: Self(key),
   1889                             remaining,
   1890                         })
   1891                 } else {
   1892                     Err(CoseKeyErr::MlDsa44CoseEncoding)
   1893                 }
   1894             })
   1895     }
   1896 }
   1897 impl<'a> FromCbor<'a> for Ed25519PubKey<&'a [u8]> {
   1898     type Err = CoseKeyErr;
   1899     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   1900         /// `crv` COSE key type parameter for [`OKP`] as defined by
   1901         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   1902         const CRV: u8 = cbor::NEG_ONE;
   1903         /// `Ed25519` COSE elliptic curve as defined by
   1904         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves).
   1905         const ED25519: u8 = cbor::SIX;
   1906         /// `x` COSE key type parameter for [`OKP`] as defined by
   1907         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   1908         const X: u8 = cbor::NEG_TWO;
   1909         // `32 as u8` is OK.
   1910         /// `ed25519_dalek::PUBLIC_KEY_LENGTH` as a `u8`.
   1911         #[expect(
   1912             clippy::as_conversions,
   1913             clippy::cast_possible_truncation,
   1914             reason = "explained above and want a const"
   1915         )]
   1916         const KEY_LEN_U8: u8 = ed25519_dalek::PUBLIC_KEY_LENGTH as u8;
   1917         /// COSE header.
   1918         /// {kty:OKP,alg:EdDSA,crv:Ed25519,x:<CompressedEdwardsYPoint>}.
   1919         /// `kty` and `alg` come before `crv` and `x` since map order first
   1920         /// is done by data type and `cbor::UINT`s come before `cbor::NEG`s.
   1921         /// `kty` comes before `alg` since order is done byte-wise and
   1922         /// 1 is before 3. `crv` is before `x` since `0b001_00000` comes before
   1923         /// `0b001_00001` byte-wise.
   1924         const HEADER: [u8; 10] = [
   1925             cbor::MAP_4,
   1926             KTY,
   1927             OKP,
   1928             ALG,
   1929             EDDSA,
   1930             CRV,
   1931             ED25519,
   1932             X,
   1933             cbor::BYTES_INFO_24,
   1934             KEY_LEN_U8,
   1935         ];
   1936         cbor.split_at_checked(HEADER.len())
   1937             .ok_or(CoseKeyErr::Len)
   1938             .and_then(|(header, header_rem)| {
   1939                 if header == HEADER {
   1940                     header_rem
   1941                         .split_at_checked(ed25519_dalek::PUBLIC_KEY_LENGTH)
   1942                         .ok_or(CoseKeyErr::Len)
   1943                         .map(|(key, remaining)| CborSuccess {
   1944                             value: Self(key),
   1945                             remaining,
   1946                         })
   1947                 } else {
   1948                     Err(CoseKeyErr::Ed25519CoseEncoding)
   1949                 }
   1950             })
   1951     }
   1952 }
   1953 impl<'a> FromCbor<'a> for UncompressedP256PubKey<'a> {
   1954     type Err = CoseKeyErr;
   1955     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   1956         /// `crv` COSE key type parameter for [`EC2`] as defined by
   1957         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   1958         const CRV: u8 = cbor::NEG_ONE;
   1959         /// `P-256` COSE elliptic curve as defined by
   1960         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves).
   1961         const P256: u8 = cbor::ONE;
   1962         /// `x` COSE key type parameter for [`EC2`] as defined by
   1963         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   1964         const X: u8 = cbor::NEG_TWO;
   1965         /// `y` COSE key type parameter for [`EC2`] as defined by
   1966         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   1967         const Y: u8 = cbor::NEG_THREE;
   1968         /// Number of bytes the x-coordinate takes.
   1969         const X_LEN: usize = <NistP256 as Curve>::FieldBytesSize::INT;
   1970         // `32 as u8` is OK.
   1971         /// `X_LEN` as a `u8`.
   1972         #[expect(
   1973             clippy::as_conversions,
   1974             clippy::cast_possible_truncation,
   1975             reason = "explained above and want a const"
   1976         )]
   1977         const X_LEN_U8: u8 = X_LEN as u8;
   1978         /// Number of bytes the y-coordinate takes.
   1979         const Y_LEN: usize = <NistP256 as Curve>::FieldBytesSize::INT;
   1980         // `32 as u8` is OK.
   1981         /// `Y_LEN` as a `u8`.
   1982         #[expect(
   1983             clippy::as_conversions,
   1984             clippy::cast_possible_truncation,
   1985             reason = "explained above and want a const"
   1986         )]
   1987         const Y_LEN_U8: u8 = Y_LEN as u8;
   1988         /// COSE header.
   1989         // {kty:EC2,alg:ES256,crv:P-256,x:<affine x-coordinate>,...}.
   1990         /// `kty` and `alg` come before `crv`, `x`, and `y` since map order first
   1991         /// is done by data type and `cbor::UINT`s come before `cbor::NEG`s.
   1992         /// `kty` comes before `alg` since order is done byte-wise and
   1993         /// 1 is before 3. `crv` is before `x` which is before `y` since
   1994         /// `0b001_00000` comes before `0b001_00001` which comes before
   1995         /// `0b001_00010` byte-wise.
   1996         const HEADER: [u8; 10] = [
   1997             cbor::MAP_5,
   1998             KTY,
   1999             EC2,
   2000             ALG,
   2001             ES256,
   2002             CRV,
   2003             P256,
   2004             X,
   2005             cbor::BYTES_INFO_24,
   2006             X_LEN_U8,
   2007         ];
   2008         /// {...y:<affine y-coordinate>}.
   2009         const Y_META: [u8; 3] = [Y, cbor::BYTES_INFO_24, Y_LEN_U8];
   2010         cbor.split_at_checked(HEADER.len())
   2011             .ok_or(CoseKeyErr::Len)
   2012             .and_then(|(header, header_rem)| {
   2013                 if header == HEADER {
   2014                     header_rem
   2015                         .split_at_checked(X_LEN)
   2016                         .ok_or(CoseKeyErr::Len)
   2017                         .and_then(|(x, x_rem)| {
   2018                             x_rem
   2019                                 .split_at_checked(Y_META.len())
   2020                                 .ok_or(CoseKeyErr::Len)
   2021                                 .and_then(|(y_meta, y_meta_rem)| {
   2022                                     if y_meta == Y_META {
   2023                                         y_meta_rem
   2024                                             .split_at_checked(Y_LEN)
   2025                                             .ok_or(CoseKeyErr::Len)
   2026                                             .map(|(y, remaining)| CborSuccess {
   2027                                                 value: Self(x, y),
   2028                                                 remaining,
   2029                                             })
   2030                                     } else {
   2031                                         Err(CoseKeyErr::P256CoseEncoding)
   2032                                     }
   2033                                 })
   2034                         })
   2035                 } else {
   2036                     Err(CoseKeyErr::P256CoseEncoding)
   2037                 }
   2038             })
   2039     }
   2040 }
   2041 impl<'a> FromCbor<'a> for UncompressedP384PubKey<'a> {
   2042     type Err = CoseKeyErr;
   2043     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   2044         /// `crv` COSE key type parameter for [`EC2`] as defined by
   2045         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   2046         const CRV: u8 = cbor::NEG_ONE;
   2047         /// `P-384` COSE elliptic curve as defined by
   2048         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves).
   2049         const P384: u8 = cbor::TWO;
   2050         /// `x` COSE key type parameter for [`EC2`] as defined by
   2051         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   2052         const X: u8 = cbor::NEG_TWO;
   2053         /// `y` COSE key type parameter for [`EC2`] as defined by
   2054         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   2055         const Y: u8 = cbor::NEG_THREE;
   2056         /// Number of bytes the x-coordinate takes.
   2057         const X_LEN: usize = <NistP384 as Curve>::FieldBytesSize::INT;
   2058         // `48 as u8` is OK.
   2059         /// `X_LEN` as a `u8`.
   2060         #[expect(
   2061             clippy::as_conversions,
   2062             clippy::cast_possible_truncation,
   2063             reason = "explained above and want a const"
   2064         )]
   2065         const X_LEN_U8: u8 = X_LEN as u8;
   2066         /// Number of bytes the y-coordinate takes.
   2067         const Y_LEN: usize = <NistP384 as Curve>::FieldBytesSize::INT;
   2068         // `48 as u8` is OK.
   2069         /// `Y_LEN` as a `u8`.
   2070         #[expect(
   2071             clippy::as_conversions,
   2072             clippy::cast_possible_truncation,
   2073             reason = "explained above and want a const"
   2074         )]
   2075         const Y_LEN_U8: u8 = Y_LEN as u8;
   2076         /// COSE header.
   2077         // {kty:EC2,alg:ES384,crv:P-384,x:<affine x-coordinate>,...}.
   2078         /// `kty` and `alg` come before `crv`, `x`, and `y` since map order first
   2079         /// is done by data type and `cbor::UINT`s come before `cbor::NEG`s.
   2080         /// `kty` comes before `alg` since order is done byte-wise and
   2081         /// 1 is before 3. `crv` is before `x` which is before `y` since
   2082         /// `0b001_00000` comes before `0b001_00001` which comes before
   2083         /// `0b001_00010` byte-wise.
   2084         const HEADER: [u8; 11] = [
   2085             cbor::MAP_5,
   2086             KTY,
   2087             EC2,
   2088             ALG,
   2089             cbor::NEG_INFO_24,
   2090             ES384,
   2091             CRV,
   2092             P384,
   2093             X,
   2094             cbor::BYTES_INFO_24,
   2095             X_LEN_U8,
   2096         ];
   2097         /// {...y:<affine y-coordinate>}.
   2098         const Y_META: [u8; 3] = [Y, cbor::BYTES_INFO_24, Y_LEN_U8];
   2099         cbor.split_at_checked(HEADER.len())
   2100             .ok_or(CoseKeyErr::Len)
   2101             .and_then(|(header, header_rem)| {
   2102                 if header == HEADER {
   2103                     header_rem
   2104                         .split_at_checked(X_LEN)
   2105                         .ok_or(CoseKeyErr::Len)
   2106                         .and_then(|(x, x_rem)| {
   2107                             x_rem
   2108                                 .split_at_checked(Y_META.len())
   2109                                 .ok_or(CoseKeyErr::Len)
   2110                                 .and_then(|(y_meta, y_meta_rem)| {
   2111                                     if y_meta == Y_META {
   2112                                         y_meta_rem
   2113                                             .split_at_checked(Y_LEN)
   2114                                             .ok_or(CoseKeyErr::Len)
   2115                                             .map(|(y, remaining)| CborSuccess {
   2116                                                 value: Self(x, y),
   2117                                                 remaining,
   2118                                             })
   2119                                     } else {
   2120                                         Err(CoseKeyErr::P384CoseEncoding)
   2121                                     }
   2122                                 })
   2123                         })
   2124                 } else {
   2125                     Err(CoseKeyErr::P384CoseEncoding)
   2126                 }
   2127             })
   2128     }
   2129 }
   2130 impl<'a> FromCbor<'a> for RsaPubKey<&'a [u8]> {
   2131     type Err = CoseKeyErr;
   2132     #[expect(
   2133         clippy::arithmetic_side_effects,
   2134         clippy::big_endian_bytes,
   2135         clippy::indexing_slicing,
   2136         clippy::missing_asserts_for_indexing,
   2137         reason = "comments justify their correctness"
   2138     )]
   2139     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   2140         /// `n` COSE key type parameter for [`RSA`] as defined by
   2141         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   2142         const N: u8 = cbor::NEG_ONE;
   2143         /// `e` COSE key type parameter for [`RSA`] as defined by
   2144         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   2145         const E: u8 = cbor::NEG_TWO;
   2146         /// COSE header.
   2147         /// {kty:RSA,alg:RS256,n:<RSA modulus>,...}.
   2148         /// `kty` and `alg` come before `n` and `e` since map order first
   2149         /// is done by data type and `cbor::UINT`s come before `cbor::NEG`s.
   2150         /// `kty` comes before `alg` since order is done byte-wise and
   2151         /// 1 is before 3. `n` is before `e` since `0b001_00000` comes before
   2152         /// `0b001_00001` byte-wise.
   2153         ///
   2154         /// Note `RS256` COSE algorithm as defined by
   2155         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#algorithms)
   2156         /// is encoded as -257 which is encoded in CBOR as `[cbor::NEG_INFO_25, 1, 0]` since
   2157         /// |-257| - 1 = 256 which takes two bytes to encode as 1, 0 in big-endian.
   2158         /// Ditto for a byte string of length 0x100 to 0xFFFF inclusively replacing `cbor::NEG_INFO_25` with
   2159         /// `cbor::BYTES_INFO_25`.
   2160         ///
   2161         /// Recall that [`RsaPubKey`] requires the modulus to be at least 256 bytes in length but no greater than
   2162         /// 2048 bytes in length; thus we know a valid and allowed `n` will have length whose metadata
   2163         /// takes exactly two bytes.
   2164         const HEADER: [u8; 9] = [
   2165             cbor::MAP_4,
   2166             KTY,
   2167             RSA,
   2168             ALG,
   2169             cbor::NEG_INFO_25,
   2170             1,
   2171             0,
   2172             N,
   2173             cbor::BYTES_INFO_25,
   2174         ];
   2175         cbor.split_at_checked(HEADER.len()).ok_or(CoseKeyErr::Len).and_then(|(header, header_rem)| {
   2176             if header == HEADER {
   2177                 header_rem.split_at_checked(2).ok_or(CoseKeyErr::Len).and_then(|(n_len_slice, n_len_rem)| {
   2178                     let mut len = [0; 2];
   2179                     len.copy_from_slice(n_len_slice);
   2180                     // cbor uints are in big-endian.
   2181                     let n_len = usize::from(u16::from_be_bytes(len));
   2182                     if n_len > 255 {
   2183                         n_len_rem.split_at_checked(n_len).ok_or(CoseKeyErr::Len).and_then(|(n, n_rem)| {
   2184                             n_rem.split_at_checked(2).ok_or(CoseKeyErr::RsaCoseEncoding).and_then(|(e_meta, e_meta_rem)| {
   2185                                 // `e_meta.len() == 2`, so this is fine.
   2186                                 if e_meta[0] == E {
   2187                                     // `e_meta.len() == 2`, so this is fine.
   2188                                     let e_meta_len = e_meta[1];
   2189                                     if e_meta_len & cbor::BYTES == cbor::BYTES {
   2190                                         let e_len = usize::from(e_meta_len ^ cbor::BYTES);
   2191                                         if e_len < 5 {
   2192                                             e_meta_rem.split_at_checked(e_len).ok_or(CoseKeyErr::Len).and_then(|(e_slice, remaining)| {
   2193                                                 e_slice.first().ok_or(CoseKeyErr::Len).and_then(|e_first| {
   2194                                                     // We ensure the leading byte is not 0; otherwise the
   2195                                                     // exponent is not properly encoded. Note the exponent
   2196                                                     //  can never be 0.
   2197                                                     if *e_first > 0 {
   2198                                                         let mut e = [0; 4];
   2199                                                         // `e_slice.len()` is `e_len` which is less than 5.
   2200                                                         // We also know it is greater than 0 since `e_slice.first()` did not err.
   2201                                                         // Thus this won't `panic`.
   2202                                                         e[4 - e_len..].copy_from_slice(e_slice);
   2203                                                         Self::try_from((n, u32::from_be_bytes(e))).map_err(CoseKeyErr::RsaPubKey).map(|value| CborSuccess { value, remaining, } )
   2204                                                     } else {
   2205                                                         Err(CoseKeyErr::RsaCoseEncoding)
   2206                                                     }
   2207                                                 })
   2208                                             })
   2209                                         } else {
   2210                                             Err(CoseKeyErr::RsaExponentTooLarge)
   2211                                         }
   2212                                     } else {
   2213                                         Err(CoseKeyErr::RsaCoseEncoding)
   2214                                     }
   2215                                 } else {
   2216                                     Err(CoseKeyErr::RsaCoseEncoding)
   2217                                 }
   2218                             })
   2219                         })
   2220                     } else {
   2221                         Err(CoseKeyErr::RsaCoseEncoding)
   2222                     }
   2223                 })
   2224             } else {
   2225                 Err(CoseKeyErr::RsaCoseEncoding)
   2226             }
   2227         })
   2228     }
   2229 }
   2230 /// An alleged uncompressed public key that borrows the key data.
   2231 ///
   2232 /// Note [`Self::MlDsa87`], [`Self::MlDsa65`], [`Self::MlDsa44`], and [`Self::Ed25519`] are compressed.
   2233 #[derive(Clone, Copy, Debug)]
   2234 pub enum UncompressedPubKey<'a> {
   2235     /// An alleged ML-DSA-87 public key.
   2236     MlDsa87(MlDsa87PubKey<&'a [u8]>),
   2237     /// An alleged ML-DSA-65 public key.
   2238     MlDsa65(MlDsa65PubKey<&'a [u8]>),
   2239     /// An alleged ML-DSA-44 public key.
   2240     MlDsa44(MlDsa44PubKey<&'a [u8]>),
   2241     /// An alleged Ed25519 public key.
   2242     Ed25519(Ed25519PubKey<&'a [u8]>),
   2243     /// An alleged uncompressed P-256 public key.
   2244     P256(UncompressedP256PubKey<'a>),
   2245     /// An alleged uncompressed P-384 public key.
   2246     P384(UncompressedP384PubKey<'a>),
   2247     /// An alleged RSA public key.
   2248     Rsa(RsaPubKey<&'a [u8]>),
   2249 }
   2250 impl UncompressedPubKey<'_> {
   2251     /// Validates `self` is in fact a valid public key.
   2252     ///
   2253     /// # Errors
   2254     ///
   2255     /// Errors iff `self` is not a valid public key.
   2256     #[inline]
   2257     pub fn validate(self) -> Result<(), PubKeyErr> {
   2258         match self {
   2259             Self::MlDsa87(_) | Self::MlDsa65(_) | Self::MlDsa44(_) | Self::Rsa(_) => Ok(()),
   2260             Self::Ed25519(k) => k.validate(),
   2261             Self::P256(k) => k.validate(),
   2262             Self::P384(k) => k.validate(),
   2263         }
   2264     }
   2265     /// Transforms `self` into the compressed version that owns the data.
   2266     #[inline]
   2267     #[must_use]
   2268     pub fn into_compressed(self) -> CompressedPubKeyOwned {
   2269         match self {
   2270             Self::MlDsa87(key) => CompressedPubKeyOwned::MlDsa87(key.into_owned()),
   2271             Self::MlDsa65(key) => CompressedPubKeyOwned::MlDsa65(key.into_owned()),
   2272             Self::MlDsa44(key) => CompressedPubKeyOwned::MlDsa44(key.into_owned()),
   2273             Self::Ed25519(key) => CompressedPubKeyOwned::Ed25519(key.into_owned()),
   2274             Self::P256(key) => CompressedPubKeyOwned::P256(key.into_compressed()),
   2275             Self::P384(key) => CompressedPubKeyOwned::P384(key.into_compressed()),
   2276             Self::Rsa(key) => CompressedPubKeyOwned::Rsa(key.into_owned()),
   2277         }
   2278     }
   2279 }
   2280 impl PartialEq<UncompressedPubKey<'_>> for UncompressedPubKey<'_> {
   2281     #[inline]
   2282     fn eq(&self, other: &UncompressedPubKey<'_>) -> bool {
   2283         match *self {
   2284             Self::MlDsa87(k) => matches!(*other, UncompressedPubKey::MlDsa87(k2) if k == k2),
   2285             Self::MlDsa65(k) => matches!(*other, UncompressedPubKey::MlDsa65(k2) if k == k2),
   2286             Self::MlDsa44(k) => matches!(*other, UncompressedPubKey::MlDsa44(k2) if k == k2),
   2287             Self::Ed25519(k) => matches!(*other, UncompressedPubKey::Ed25519(k2) if k == k2),
   2288             Self::P256(k) => matches!(*other, UncompressedPubKey::P256(k2) if k == k2),
   2289             Self::P384(k) => matches!(*other, UncompressedPubKey::P384(k2) if k == k2),
   2290             Self::Rsa(k) => matches!(*other, UncompressedPubKey::Rsa(k2) if k == k2),
   2291         }
   2292     }
   2293 }
   2294 impl PartialEq<&UncompressedPubKey<'_>> for UncompressedPubKey<'_> {
   2295     #[inline]
   2296     fn eq(&self, other: &&UncompressedPubKey<'_>) -> bool {
   2297         *self == **other
   2298     }
   2299 }
   2300 impl PartialEq<UncompressedPubKey<'_>> for &UncompressedPubKey<'_> {
   2301     #[inline]
   2302     fn eq(&self, other: &UncompressedPubKey<'_>) -> bool {
   2303         **self == *other
   2304     }
   2305 }
   2306 impl Eq for UncompressedPubKey<'_> {}
   2307 /// An alleged compressed public key.
   2308 ///
   2309 /// Note [`Self::Rsa`] is uncompressed.
   2310 #[derive(Clone, Copy, Debug)]
   2311 pub enum CompressedPubKey<T, T2, T3, T4, T5, T6, T7> {
   2312     /// An alleged ML-DSA-87 public key.
   2313     MlDsa87(MlDsa87PubKey<T>),
   2314     /// An alleged ML-DSA-65 public key.
   2315     MlDsa65(MlDsa65PubKey<T2>),
   2316     /// An alleged ML-DSA-44 public key.
   2317     MlDsa44(MlDsa44PubKey<T3>),
   2318     /// An alleged Ed25519 public key.
   2319     Ed25519(Ed25519PubKey<T4>),
   2320     /// An alleged compressed P-256 public key.
   2321     P256(CompressedP256PubKey<T5>),
   2322     /// An alleged compressed P-384 public key.
   2323     P384(CompressedP384PubKey<T6>),
   2324     /// An alleged RSA public key.
   2325     Rsa(RsaPubKey<T7>),
   2326 }
   2327 /// `CompressedPubKey` that owns the key data.
   2328 pub type CompressedPubKeyOwned = CompressedPubKey<
   2329     Box<[u8]>,
   2330     Box<[u8]>,
   2331     Box<[u8]>,
   2332     [u8; ed25519_dalek::PUBLIC_KEY_LENGTH],
   2333     [u8; <NistP256 as Curve>::FieldBytesSize::INT],
   2334     [u8; <NistP384 as Curve>::FieldBytesSize::INT],
   2335     Box<[u8]>,
   2336 >;
   2337 /// `CompressedPubKey` that borrows the key data.
   2338 pub type CompressedPubKeyBorrowed<'a> =
   2339     CompressedPubKey<&'a [u8], &'a [u8], &'a [u8], &'a [u8], &'a [u8], &'a [u8], &'a [u8]>;
   2340 impl CompressedPubKeyBorrowed<'_> {
   2341     /// Validates `self` is in fact a valid public key.
   2342     ///
   2343     /// # Errors
   2344     ///
   2345     /// Errors iff `self` is not a valid public key.
   2346     #[inline]
   2347     pub fn validate(self) -> Result<(), PubKeyErr> {
   2348         match self {
   2349             Self::MlDsa87(_) | Self::MlDsa65(_) | Self::MlDsa44(_) | Self::Rsa(_) => Ok(()),
   2350             Self::Ed25519(k) => k.validate(),
   2351             Self::P256(k) => k.validate(),
   2352             Self::P384(k) => k.validate(),
   2353         }
   2354     }
   2355 }
   2356 impl<
   2357     'a: 'b,
   2358     'b,
   2359     T: AsRef<[u8]>,
   2360     T2: AsRef<[u8]>,
   2361     T3: AsRef<[u8]>,
   2362     T4: AsRef<[u8]>,
   2363     T5: AsRef<[u8]>,
   2364     T6: AsRef<[u8]>,
   2365     T7: AsRef<[u8]>,
   2366 > From<&'a CompressedPubKey<T, T2, T3, T4, T5, T6, T7>> for CompressedPubKeyBorrowed<'b>
   2367 {
   2368     #[inline]
   2369     fn from(value: &'a CompressedPubKey<T, T2, T3, T4, T5, T6, T7>) -> Self {
   2370         match *value {
   2371             CompressedPubKey::MlDsa87(ref val) => Self::MlDsa87(MlDsa87PubKey(val.0.as_ref())),
   2372             CompressedPubKey::MlDsa65(ref val) => Self::MlDsa65(MlDsa65PubKey(val.0.as_ref())),
   2373             CompressedPubKey::MlDsa44(ref val) => Self::MlDsa44(MlDsa44PubKey(val.0.as_ref())),
   2374             CompressedPubKey::Ed25519(ref val) => Self::Ed25519(Ed25519PubKey(val.0.as_ref())),
   2375             CompressedPubKey::P256(ref val) => Self::P256(CompressedP256PubKey {
   2376                 x: val.x.as_ref(),
   2377                 y_is_odd: val.y_is_odd,
   2378             }),
   2379             CompressedPubKey::P384(ref val) => Self::P384(CompressedP384PubKey {
   2380                 x: val.x.as_ref(),
   2381                 y_is_odd: val.y_is_odd,
   2382             }),
   2383             CompressedPubKey::Rsa(ref val) => Self::Rsa(RsaPubKey(val.0.as_ref(), val.1)),
   2384         }
   2385     }
   2386 }
   2387 impl<
   2388     T: PartialEq<T8>,
   2389     T8: PartialEq<T>,
   2390     T2: PartialEq<T9>,
   2391     T9: PartialEq<T2>,
   2392     T3: PartialEq<T10>,
   2393     T10: PartialEq<T3>,
   2394     T4: PartialEq<T11>,
   2395     T11: PartialEq<T4>,
   2396     T5: PartialEq<T12>,
   2397     T12: PartialEq<T5>,
   2398     T6: PartialEq<T13>,
   2399     T13: PartialEq<T6>,
   2400     T7: PartialEq<T14>,
   2401     T14: PartialEq<T7>,
   2402 > PartialEq<CompressedPubKey<T, T2, T3, T4, T5, T6, T7>>
   2403     for CompressedPubKey<T8, T9, T10, T11, T12, T13, T14>
   2404 {
   2405     #[inline]
   2406     fn eq(&self, other: &CompressedPubKey<T, T2, T3, T4, T5, T6, T7>) -> bool {
   2407         match *self {
   2408             Self::MlDsa87(ref val) => {
   2409                 matches!(*other, CompressedPubKey::MlDsa87(ref val2) if val == val2)
   2410             }
   2411             Self::MlDsa65(ref val) => {
   2412                 matches!(*other, CompressedPubKey::MlDsa65(ref val2) if val == val2)
   2413             }
   2414             Self::MlDsa44(ref val) => {
   2415                 matches!(*other, CompressedPubKey::MlDsa44(ref val2) if val == val2)
   2416             }
   2417             Self::Ed25519(ref val) => {
   2418                 matches!(*other, CompressedPubKey::Ed25519(ref val2) if val == val2)
   2419             }
   2420             Self::P256(ref val) => {
   2421                 matches!(*other, CompressedPubKey::P256(ref val2) if val == val2)
   2422             }
   2423             Self::P384(ref val) => {
   2424                 matches!(*other, CompressedPubKey::P384(ref val2) if val == val2)
   2425             }
   2426             Self::Rsa(ref val) => matches!(*other, CompressedPubKey::Rsa(ref val2) if val == val2),
   2427         }
   2428     }
   2429 }
   2430 impl<
   2431     T: PartialEq<T8>,
   2432     T8: PartialEq<T>,
   2433     T2: PartialEq<T9>,
   2434     T9: PartialEq<T2>,
   2435     T3: PartialEq<T10>,
   2436     T10: PartialEq<T3>,
   2437     T4: PartialEq<T11>,
   2438     T11: PartialEq<T4>,
   2439     T5: PartialEq<T12>,
   2440     T12: PartialEq<T5>,
   2441     T6: PartialEq<T13>,
   2442     T13: PartialEq<T6>,
   2443     T7: PartialEq<T14>,
   2444     T14: PartialEq<T7>,
   2445 > PartialEq<CompressedPubKey<T, T2, T3, T4, T5, T6, T7>>
   2446     for &CompressedPubKey<T8, T9, T10, T11, T12, T13, T14>
   2447 {
   2448     #[inline]
   2449     fn eq(&self, other: &CompressedPubKey<T, T2, T3, T4, T5, T6, T7>) -> bool {
   2450         **self == *other
   2451     }
   2452 }
   2453 impl<
   2454     T: PartialEq<T8>,
   2455     T8: PartialEq<T>,
   2456     T2: PartialEq<T9>,
   2457     T9: PartialEq<T2>,
   2458     T3: PartialEq<T10>,
   2459     T10: PartialEq<T3>,
   2460     T4: PartialEq<T11>,
   2461     T11: PartialEq<T4>,
   2462     T5: PartialEq<T12>,
   2463     T12: PartialEq<T5>,
   2464     T6: PartialEq<T13>,
   2465     T13: PartialEq<T6>,
   2466     T7: PartialEq<T14>,
   2467     T14: PartialEq<T7>,
   2468 > PartialEq<&CompressedPubKey<T, T2, T3, T4, T5, T6, T7>>
   2469     for CompressedPubKey<T8, T9, T10, T11, T12, T13, T14>
   2470 {
   2471     #[inline]
   2472     fn eq(&self, other: &&CompressedPubKey<T, T2, T3, T4, T5, T6, T7>) -> bool {
   2473         *self == **other
   2474     }
   2475 }
   2476 impl<T: Eq, T2: Eq, T3: Eq, T4: Eq, T5: Eq, T6: Eq, T7: Eq> Eq
   2477     for CompressedPubKey<T, T2, T3, T4, T5, T6, T7>
   2478 {
   2479 }
   2480 impl<'a> FromCbor<'a> for UncompressedPubKey<'a> {
   2481     type Err = CoseKeyErr;
   2482     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   2483         // {kty:<type>...}.
   2484         cbor.get(2)
   2485             .ok_or(CoseKeyErr::Len)
   2486             .and_then(|kty| match *kty {
   2487                 OKP => Ed25519PubKey::from_cbor(cbor).map(|key| CborSuccess {
   2488                     value: Self::Ed25519(key.value),
   2489                     remaining: key.remaining,
   2490                 }),
   2491                 // {kty:EC2,alg:ES256|ES384,...}
   2492                 EC2 => cbor.get(4).ok_or(CoseKeyErr::Len).and_then(|alg| {
   2493                     if *alg == ES256 {
   2494                         UncompressedP256PubKey::from_cbor(cbor).map(|key| CborSuccess {
   2495                             value: Self::P256(key.value),
   2496                             remaining: key.remaining,
   2497                         })
   2498                     } else {
   2499                         UncompressedP384PubKey::from_cbor(cbor).map(|key| CborSuccess {
   2500                             value: Self::P384(key.value),
   2501                             remaining: key.remaining,
   2502                         })
   2503                     }
   2504                 }),
   2505                 RSA => RsaPubKey::from_cbor(cbor).map(|key| CborSuccess {
   2506                     value: Self::Rsa(key.value),
   2507                     remaining: key.remaining,
   2508                 }),
   2509                 // {kty:AKP,alg:ML-DSA-87|ML-DSA-65|ML-DSA-44,...}
   2510                 AKP => cbor
   2511                     .get(5)
   2512                     .ok_or(CoseKeyErr::Len)
   2513                     .and_then(|alg| match *alg {
   2514                         MLDSA44 => MlDsa44PubKey::from_cbor(cbor).map(|key| CborSuccess {
   2515                             value: Self::MlDsa44(key.value),
   2516                             remaining: key.remaining,
   2517                         }),
   2518                         MLDSA65 => MlDsa65PubKey::from_cbor(cbor).map(|key| CborSuccess {
   2519                             value: Self::MlDsa65(key.value),
   2520                             remaining: key.remaining,
   2521                         }),
   2522                         _ => MlDsa87PubKey::from_cbor(cbor).map(|key| CborSuccess {
   2523                             value: Self::MlDsa87(key.value),
   2524                             remaining: key.remaining,
   2525                         }),
   2526                     }),
   2527                 _ => Err(CoseKeyErr::CoseKeyType),
   2528             })
   2529     }
   2530 }
   2531 /// Length of AAGUID.
   2532 const AAGUID_LEN: usize = 16;
   2533 /// 16 bytes representing an
   2534 /// [Authenticator Attestation Globally Unique Identifier (AAGUID)](https://www.w3.org/TR/webauthn-3/#aaguid).
   2535 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   2536 pub struct Aaguid<'a>(&'a [u8]);
   2537 impl<'a> Aaguid<'a> {
   2538     /// Returns the contained data.
   2539     #[inline]
   2540     #[must_use]
   2541     pub const fn data(self) -> &'a [u8] {
   2542         self.0
   2543     }
   2544 }
   2545 impl<'a: 'b, 'b> TryFrom<&'a [u8]> for Aaguid<'b> {
   2546     type Error = AaguidErr;
   2547     #[inline]
   2548     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
   2549         if value.len() == AAGUID_LEN {
   2550             Ok(Self(value))
   2551         } else {
   2552             Err(AaguidErr)
   2553         }
   2554     }
   2555 }
   2556 /// [Attested credential data](https://www.w3.org/TR/webauthn-3/#attested-credential-data).
   2557 #[derive(Debug)]
   2558 pub struct AttestedCredentialData<'a> {
   2559     /// [`aaguid`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata-aaguid).
   2560     pub aaguid: Aaguid<'a>,
   2561     /// [`credentialId`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata-credentialid).
   2562     pub credential_id: CredentialId<&'a [u8]>,
   2563     /// [`credentialPublicKey`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata-credentialpublickey).
   2564     pub credential_public_key: UncompressedPubKey<'a>,
   2565 }
   2566 impl<'a> FromCbor<'a> for AttestedCredentialData<'a> {
   2567     type Err = AttestedCredentialDataErr;
   2568     #[expect(clippy::big_endian_bytes, reason = "CBOR integers are big-endian")]
   2569     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   2570         /// Number of bytes the `CredentialId` length is encoded into.
   2571         const CRED_LEN_LEN: usize = 2;
   2572         cbor.split_at_checked(AAGUID_LEN)
   2573             .ok_or(AttestedCredentialDataErr::Len)
   2574             .and_then(|(aaguid, aaguid_rem)| {
   2575                 aaguid_rem
   2576                     .split_at_checked(CRED_LEN_LEN)
   2577                     .ok_or(AttestedCredentialDataErr::Len)
   2578                     .and_then(|(cred_len_slice, cred_len_rem)| {
   2579                         let mut cred_len = [0; CRED_LEN_LEN];
   2580                         cred_len.copy_from_slice(cred_len_slice);
   2581                         // `credentialIdLength` is in big-endian.
   2582                         cred_len_rem
   2583                             .split_at_checked(usize::from(u16::from_be_bytes(cred_len)))
   2584                             .ok_or(AttestedCredentialDataErr::Len)
   2585                             .and_then(|(cred_id, cred_id_rem)| {
   2586                                 CredentialId::from_slice(cred_id)
   2587                                     .map_err(AttestedCredentialDataErr::CredentialId)
   2588                                     .and_then(|credential_id| {
   2589                                         UncompressedPubKey::from_cbor(cred_id_rem)
   2590                                             .map_err(AttestedCredentialDataErr::CoseKey)
   2591                                             .map(|cose| CborSuccess {
   2592                                                 value: Self {
   2593                                                     aaguid: Aaguid(aaguid),
   2594                                                     credential_id,
   2595                                                     credential_public_key: cose.value,
   2596                                                 },
   2597                                                 remaining: cose.remaining,
   2598                                             })
   2599                                     })
   2600                             })
   2601                     })
   2602             })
   2603     }
   2604 }
   2605 /// [Authenticator data](https://www.w3.org/TR/webauthn-3/#authenticator-data).
   2606 #[derive(Debug)]
   2607 pub struct AuthenticatorData<'a> {
   2608     /// [`rpIdHash`](https://www.w3.org/TR/webauthn-3/#authdata-rpidhash).
   2609     rp_id_hash: &'a [u8],
   2610     /// [`flags`](https://www.w3.org/TR/webauthn-3/#authdata-flags).
   2611     flags: Flag,
   2612     /// [`signCount`](https://www.w3.org/TR/webauthn-3/#authdata-signcount).
   2613     sign_count: u32,
   2614     /// [`attestedCredentialData`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata).
   2615     attested_credential_data: AttestedCredentialData<'a>,
   2616     /// [`extensions`](https://www.w3.org/TR/webauthn-3/#authdata-extensions).
   2617     extensions: AuthenticatorExtensionOutput,
   2618 }
   2619 impl<'a> AuthenticatorData<'a> {
   2620     /// [`rpIdHash`](https://www.w3.org/TR/webauthn-3/#authdata-rpidhash).
   2621     #[inline]
   2622     #[must_use]
   2623     pub const fn rp_id_hash(&self) -> &'a [u8] {
   2624         self.rp_id_hash
   2625     }
   2626     /// [`flags`](https://www.w3.org/TR/webauthn-3/#authdata-flags).
   2627     #[inline]
   2628     #[must_use]
   2629     pub const fn flags(&self) -> Flag {
   2630         self.flags
   2631     }
   2632     /// [`signCount`](https://www.w3.org/TR/webauthn-3/#authdata-signcount).
   2633     #[inline]
   2634     #[must_use]
   2635     pub const fn sign_count(&self) -> u32 {
   2636         self.sign_count
   2637     }
   2638     /// [`attestedCredentialData`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata).
   2639     #[inline]
   2640     #[must_use]
   2641     pub const fn attested_credential_data(&self) -> &AttestedCredentialData<'a> {
   2642         &self.attested_credential_data
   2643     }
   2644     /// [`extensions`](https://www.w3.org/TR/webauthn-3/#authdata-extensions).
   2645     #[inline]
   2646     #[must_use]
   2647     pub const fn extensions(&self) -> AuthenticatorExtensionOutput {
   2648         self.extensions
   2649     }
   2650 }
   2651 impl<'a: 'b, 'b> TryFrom<&'a [u8]> for AuthenticatorData<'b> {
   2652     type Error = AuthenticatorDataErr;
   2653     /// Deserializes `value` based on the
   2654     /// [authenticator data structure](https://www.w3.org/TR/webauthn-3/#table-authData).
   2655     #[expect(
   2656         clippy::panic_in_result_fn,
   2657         reason = "we want to crash when there is a bug"
   2658     )]
   2659     #[inline]
   2660     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
   2661         Self::from_cbor(value)
   2662             .map_err(AuthenticatorDataErr::from)
   2663             .map(|success| {
   2664                 assert!(
   2665                     success.remaining.is_empty(),
   2666                     "there is a bug in AuthenticatorData::from_cbor"
   2667                 );
   2668                 success.value
   2669             })
   2670     }
   2671 }
   2672 impl<'a> AuthData<'a> for AuthenticatorData<'a> {
   2673     type UpBitErr = Infallible;
   2674     type CredData = AttestedCredentialData<'a>;
   2675     type Ext = AuthenticatorExtensionOutput;
   2676     fn contains_at_bit() -> bool {
   2677         true
   2678     }
   2679     fn user_is_not_present() -> Result<(), Self::UpBitErr> {
   2680         Ok(())
   2681     }
   2682     fn new(
   2683         rp_id_hash: &'a [u8],
   2684         flags: Flag,
   2685         sign_count: u32,
   2686         attested_credential_data: Self::CredData,
   2687         extensions: Self::Ext,
   2688     ) -> Self {
   2689         Self {
   2690             rp_id_hash,
   2691             flags,
   2692             sign_count,
   2693             attested_credential_data,
   2694             extensions,
   2695         }
   2696     }
   2697     fn rp_hash(&self) -> &'a [u8] {
   2698         self.rp_id_hash
   2699     }
   2700     fn flag(&self) -> Flag {
   2701         self.flags
   2702     }
   2703 }
   2704 /// [None](https://www.w3.org/TR/webauthn-3/#sctn-none-attestation).
   2705 struct NoneAttestation;
   2706 impl FromCbor<'_> for NoneAttestation {
   2707     type Err = AttestationErr;
   2708     fn from_cbor(cbor: &[u8]) -> Result<CborSuccess<'_, Self>, Self::Err> {
   2709         cbor.split_first()
   2710             .ok_or(AttestationErr::Len)
   2711             .and_then(|(map, remaining)| {
   2712                 if *map == cbor::MAP_0 {
   2713                     Ok(CborSuccess {
   2714                         value: Self,
   2715                         remaining,
   2716                     })
   2717                 } else {
   2718                     Err(AttestationErr::NoneFormat)
   2719                 }
   2720             })
   2721     }
   2722 }
   2723 /// A 4627-byte slice that allegedly represents an ML-DSA-87 signature.
   2724 struct MlDsa87Signature<'a>(&'a [u8]);
   2725 impl<'a> FromCbor<'a> for MlDsa87Signature<'a> {
   2726     type Err = AttestationErr;
   2727     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   2728         /// CBOR metadata describing the signature.
   2729         /// 18 * 256 + 19 = 4627.
   2730         const HEADER: [u8; 3] = [cbor::BYTES_INFO_25, 18, 19];
   2731         cbor.split_at_checked(HEADER.len())
   2732             .ok_or(AttestationErr::Len)
   2733             .and_then(|(header, header_rem)| {
   2734                 if header == HEADER {
   2735                     header_rem
   2736                         .split_at_checked(4627)
   2737                         .ok_or(AttestationErr::Len)
   2738                         .map(|(sig, remaining)| CborSuccess {
   2739                             value: Self(sig),
   2740                             remaining,
   2741                         })
   2742                 } else {
   2743                     Err(AttestationErr::PackedFormatCborMlDsa87Signature)
   2744                 }
   2745             })
   2746     }
   2747 }
   2748 /// A 3309-byte slice that allegedly represents an ML-DSA-65 signature.
   2749 struct MlDsa65Signature<'a>(&'a [u8]);
   2750 impl<'a> FromCbor<'a> for MlDsa65Signature<'a> {
   2751     type Err = AttestationErr;
   2752     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   2753         /// CBOR metadata describing the signature.
   2754         /// 12 * 256 + 237 = 3309.
   2755         const HEADER: [u8; 3] = [cbor::BYTES_INFO_25, 12, 237];
   2756         cbor.split_at_checked(HEADER.len())
   2757             .ok_or(AttestationErr::Len)
   2758             .and_then(|(header, header_rem)| {
   2759                 if header == HEADER {
   2760                     header_rem
   2761                         .split_at_checked(3309)
   2762                         .ok_or(AttestationErr::Len)
   2763                         .map(|(sig, remaining)| CborSuccess {
   2764                             value: Self(sig),
   2765                             remaining,
   2766                         })
   2767                 } else {
   2768                     Err(AttestationErr::PackedFormatCborMlDsa65Signature)
   2769                 }
   2770             })
   2771     }
   2772 }
   2773 /// A 2420-byte slice that allegedly represents an ML-DSA-44 signature.
   2774 struct MlDsa44Signature<'a>(&'a [u8]);
   2775 impl<'a> FromCbor<'a> for MlDsa44Signature<'a> {
   2776     type Err = AttestationErr;
   2777     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   2778         /// CBOR metadata describing the signature.
   2779         /// 9 * 256 + 116 = 2420.
   2780         const HEADER: [u8; 3] = [cbor::BYTES_INFO_25, 9, 116];
   2781         cbor.split_at_checked(HEADER.len())
   2782             .ok_or(AttestationErr::Len)
   2783             .and_then(|(header, header_rem)| {
   2784                 if header == HEADER {
   2785                     header_rem
   2786                         .split_at_checked(2420)
   2787                         .ok_or(AttestationErr::Len)
   2788                         .map(|(sig, remaining)| CborSuccess {
   2789                             value: Self(sig),
   2790                             remaining,
   2791                         })
   2792                 } else {
   2793                     Err(AttestationErr::PackedFormatCborMlDsa44Signature)
   2794                 }
   2795             })
   2796     }
   2797 }
   2798 /// A 64-byte slice that allegedly represents an Ed25519 signature.
   2799 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   2800 pub struct Ed25519Signature<'a>(&'a [u8]);
   2801 impl<'a> Ed25519Signature<'a> {
   2802     /// Returns signature.
   2803     #[inline]
   2804     #[must_use]
   2805     pub const fn data(self) -> &'a [u8] {
   2806         self.0
   2807     }
   2808     /// Transforms `self` into `Signature`.
   2809     fn into_sig(self) -> Signature {
   2810         let mut sig = [0; ed25519_dalek::SIGNATURE_LENGTH];
   2811         sig.copy_from_slice(self.0);
   2812         Signature::from_bytes(&sig)
   2813     }
   2814 }
   2815 impl<'a: 'b, 'b> TryFrom<&'a [u8]> for Ed25519Signature<'b> {
   2816     type Error = Ed25519SignatureErr;
   2817     /// Interprets `value` as an Ed25519 signature.
   2818     ///
   2819     /// # Errors
   2820     ///
   2821     /// Errors iff `value.len() != 64`.
   2822     #[inline]
   2823     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
   2824         if value.len() == ed25519_dalek::SIGNATURE_LENGTH {
   2825             Ok(Self(value))
   2826         } else {
   2827             Err(Ed25519SignatureErr)
   2828         }
   2829     }
   2830 }
   2831 impl<'a> FromCbor<'a> for Ed25519Signature<'a> {
   2832     type Err = AttestationErr;
   2833     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   2834         // `64 as u8` is OK.
   2835         /// CBOR metadata describing the signature.
   2836         #[expect(
   2837             clippy::as_conversions,
   2838             clippy::cast_possible_truncation,
   2839             reason = "comments justify their correctness"
   2840         )]
   2841         const HEADER: [u8; 2] = [cbor::BYTES_INFO_24, ed25519_dalek::SIGNATURE_LENGTH as u8];
   2842         cbor.split_at_checked(HEADER.len())
   2843             .ok_or(AttestationErr::Len)
   2844             .and_then(|(header, header_rem)| {
   2845                 if header == HEADER {
   2846                     header_rem
   2847                         .split_at_checked(ed25519_dalek::SIGNATURE_LENGTH)
   2848                         .ok_or(AttestationErr::Len)
   2849                         .map(|(sig, remaining)| CborSuccess {
   2850                             value: Self(sig),
   2851                             remaining,
   2852                         })
   2853                 } else {
   2854                     Err(AttestationErr::PackedFormatCborEd25519Signature)
   2855                 }
   2856             })
   2857     }
   2858 }
   2859 /// An alleged DER-encoded P-256 signature using SHA-256.
   2860 struct P256DerSig<'a>(&'a [u8]);
   2861 impl<'a> FromCbor<'a> for P256DerSig<'a> {
   2862     type Err = AttestationErr;
   2863     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   2864         // ```asn
   2865         // Signature ::= SEQUENCE {
   2866         //     r INTEGER,
   2867         //     s INTEGER
   2868         // }
   2869         // ```
   2870         // We assume `r` and `s` can be _any_ 32-byte unsigned integer; thus when DER-encoded without metadata,
   2871         // the number of bytes is inclusively between 1 and 33—INTEGER is a signed integer; thus a 0 byte must
   2872         // be prepended iff the high bit is 1 (i.e., 32-byte unsigned integers with the high bit set take 33 bytes).
   2873         // With metadata this makes the lengths inclusively between 3 and 35—we add 1 for the INTEGER tag and 1
   2874         // for the number of bytes it takes to encode the integer. The total encoded length is thus inclusively
   2875         // between 8 = 1 + 1 + 2(3) and 72 = 1 + 1 + 2(35)—we add 1 for the CONSTRUCTED SEQUENCE tag and 1 for
   2876         // the number of bytes to encode the sequence. Instead of handling that specific range of lengths,
   2877         // we handle all lengths inclusively between 0 and 255.
   2878         cbor.split_first()
   2879             .ok_or(AttestationErr::Len)
   2880             .and_then(|(bytes, bytes_rem)| {
   2881                 if bytes & cbor::BYTES == cbor::BYTES {
   2882                     let len_info = bytes ^ cbor::BYTES;
   2883                     match len_info {
   2884                         ..=23 => Ok((bytes_rem, len_info)),
   2885                         24 => bytes_rem.split_first().ok_or(AttestationErr::Len).and_then(
   2886                             |(&len, len_rem)| {
   2887                                 if len > 23 {
   2888                                     Ok((len_rem, len))
   2889                                 } else {
   2890                                     Err(AttestationErr::PackedFormatCborP256Signature)
   2891                                 }
   2892                             },
   2893                         ),
   2894                         _ => Err(AttestationErr::PackedFormatCborP256Signature),
   2895                     }
   2896                     .and_then(|(rem, len)| {
   2897                         rem.split_at_checked(usize::from(len))
   2898                             .ok_or(AttestationErr::Len)
   2899                             .map(|(sig, remaining)| CborSuccess {
   2900                                 value: Self(sig),
   2901                                 remaining,
   2902                             })
   2903                     })
   2904                 } else {
   2905                     Err(AttestationErr::PackedFormatCborP256Signature)
   2906                 }
   2907             })
   2908     }
   2909 }
   2910 /// An alleged DER-encoded P-384 signature using SHA-384.
   2911 struct P384DerSig<'a>(&'a [u8]);
   2912 impl<'a> FromCbor<'a> for P384DerSig<'a> {
   2913     type Err = AttestationErr;
   2914     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   2915         // ```asn
   2916         // Signature ::= SEQUENCE {
   2917         //     r INTEGER,
   2918         //     s INTEGER
   2919         // }
   2920         // ```
   2921         // We assume `r` and `s` can be _any_ 48-byte unsigned integer; thus when DER-encoded without metadata,
   2922         // the number of bytes is inclusively between 1 and 49—INTEGER is a signed integer; thus a 0 byte must
   2923         // be prepended iff the high bit is 1 (i.e., 48-byte unsigned integers with the high bit set take 49 bytes).
   2924         // With metadata this makes the lengths inclusively between 3 and 51—we add 1 for the INTEGER tag and 1
   2925         // for the number of bytes it takes to encode the integer. The total encoded length is thus inclusively
   2926         // between 8 = 1 + 1 + 2(3) and 104 = 1 + 1 + 2(51)—we add 1 for the CONSTRUCTED SEQUENCE tag and 1 for
   2927         // the number of bytes to encode the sequence. Instead of handling that specific range of lengths,
   2928         // we handle all lengths inclusively between 0 and 255.
   2929         cbor.split_first()
   2930             .ok_or(AttestationErr::Len)
   2931             .and_then(|(bytes, bytes_rem)| {
   2932                 if bytes & cbor::BYTES == cbor::BYTES {
   2933                     let len_info = bytes ^ cbor::BYTES;
   2934                     match len_info {
   2935                         ..=23 => Ok((bytes_rem, len_info)),
   2936                         24 => bytes_rem.split_first().ok_or(AttestationErr::Len).and_then(
   2937                             |(&len, len_rem)| {
   2938                                 if len > 23 {
   2939                                     Ok((len_rem, len))
   2940                                 } else {
   2941                                     Err(AttestationErr::PackedFormatCborP384Signature)
   2942                                 }
   2943                             },
   2944                         ),
   2945                         _ => Err(AttestationErr::PackedFormatCborP384Signature),
   2946                     }
   2947                     .and_then(|(rem, len)| {
   2948                         rem.split_at_checked(usize::from(len))
   2949                             .ok_or(AttestationErr::Len)
   2950                             .map(|(sig, remaining)| CborSuccess {
   2951                                 value: Self(sig),
   2952                                 remaining,
   2953                             })
   2954                     })
   2955                 } else {
   2956                     Err(AttestationErr::PackedFormatCborP384Signature)
   2957                 }
   2958             })
   2959     }
   2960 }
   2961 /// An alleged RSASSA-PKCS1-v1_5 signature using SHA-256.
   2962 struct RsaPkcs1v15Sig<'a>(&'a [u8]);
   2963 impl<'a> FromCbor<'a> for RsaPkcs1v15Sig<'a> {
   2964     type Err = AttestationErr;
   2965     #[expect(clippy::big_endian_bytes, reason = "CBOR integers are big-endian")]
   2966     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   2967         // RSASSA-PKCS1-v1_5 signatures are the same length as the modulus. We only allow moduli consisting of
   2968         // [`MIN_RSA_N_BYTES`] to [`MAX_RSA_N_BYTES`] bytes inclusively. This means
   2969         // all signatures will use the same CBOR metadata tag (i.e., `cbor::BYTES_INFO_25`).
   2970         cbor.split_first()
   2971             .ok_or(AttestationErr::Len)
   2972             .and_then(|(bytes, bytes_rem)| {
   2973                 if *bytes == cbor::BYTES_INFO_25 {
   2974                     bytes_rem
   2975                         .split_at_checked(2)
   2976                         .ok_or(AttestationErr::Len)
   2977                         .and_then(|(len_slice, len_rem)| {
   2978                             let mut cbor_len = [0; 2];
   2979                             cbor_len.copy_from_slice(len_slice);
   2980                             // CBOR uints are big-endian.
   2981                             let len = usize::from(u16::from_be_bytes(cbor_len));
   2982                             if len > 255 {
   2983                                 len_rem
   2984                                     .split_at_checked(len)
   2985                                     .ok_or(AttestationErr::Len)
   2986                                     .map(|(sig, remaining)| CborSuccess {
   2987                                         value: Self(sig),
   2988                                         remaining,
   2989                                     })
   2990                             } else {
   2991                                 Err(AttestationErr::PackedFormatCborRs256Signature)
   2992                             }
   2993                         })
   2994                 } else {
   2995                     Err(AttestationErr::PackedFormatCborRs256Signature)
   2996                 }
   2997             })
   2998     }
   2999 }
   3000 /// [Packed](https://www.w3.org/TR/webauthn-3/#sctn-packed-attestation) signature.
   3001 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   3002 pub enum Sig<'a> {
   3003     /// Alleged ML-DSA-87.
   3004     MlDsa87(&'a [u8]),
   3005     /// Alleged ML-DSA-65.
   3006     MlDsa65(&'a [u8]),
   3007     /// Alleged ML-DSA-44.
   3008     MlDsa44(&'a [u8]),
   3009     /// Alleged Ed25519 signature.
   3010     Ed25519(Ed25519Signature<'a>),
   3011     /// Alleged DER-encoded P-256 signature using SHA-256.
   3012     P256(&'a [u8]),
   3013     /// Alleged DER-encoded P-384 signature using SHA-384.
   3014     P384(&'a [u8]),
   3015     /// Alleged RSASSA-PKCS1-v1_5 signature using SHA-256.
   3016     Rs256(&'a [u8]),
   3017 }
   3018 /// [Packed](https://www.w3.org/TR/webauthn-3/#sctn-packed-attestation).
   3019 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   3020 pub struct PackedAttestation<'a> {
   3021     /// [Attestation signature](https://www.w3.org/TR/webauthn-3/#attestation-signature).
   3022     pub signature: Sig<'a>,
   3023 }
   3024 impl<'a> FromCbor<'a> for PackedAttestation<'a> {
   3025     type Err = AttestationErr;
   3026     #[expect(
   3027         clippy::too_many_lines,
   3028         reason = "don't want to move code to an outer scope"
   3029     )]
   3030     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   3031         /// Parses `data` as packed attestation up until `x5c` _without_ the `cbor::MAP_2` or `cbor::MAP_3` header.
   3032         fn parse_to_cert_chain(data: &[u8]) -> Result<CborSuccess<'_, Sig<'_>>, AttestationErr> {
   3033             // {"alg":CoseAlgorithmIdentifier,"sig":sig_bytes...}
   3034             /// "alg" key.
   3035             const ALG: [u8; 4] = [cbor::TEXT_3, b'a', b'l', b'g'];
   3036             /// "sig" key.
   3037             const SIG: [u8; 4] = [cbor::TEXT_3, b's', b'i', b'g'];
   3038             data.split_at_checked(ALG.len())
   3039                 .ok_or(AttestationErr::Len)
   3040                 .and_then(|(alg, alg_rem)| {
   3041                     if alg == ALG {
   3042                         alg_rem.split_first().ok_or(AttestationErr::Len).and_then(
   3043                             |(cose, cose_rem)| match *cose {
   3044                                 EDDSA => cose_rem
   3045                                     .split_at_checked(SIG.len())
   3046                                     .ok_or(AttestationErr::Len)
   3047                                     .and_then(|(sig, sig_rem)| {
   3048                                         if sig == SIG {
   3049                                             Ed25519Signature::from_cbor(sig_rem).map(|success| {
   3050                                                 CborSuccess {
   3051                                                     value: Sig::Ed25519(success.value),
   3052                                                     remaining: success.remaining,
   3053                                                 }
   3054                                             })
   3055                                         } else {
   3056                                             Err(AttestationErr::PackedFormatMissingSig)
   3057                                         }
   3058                                     }),
   3059                                 ES256 => cose_rem
   3060                                     .split_at_checked(SIG.len())
   3061                                     .ok_or(AttestationErr::Len)
   3062                                     .and_then(|(sig, sig_rem)| {
   3063                                         if sig == SIG {
   3064                                             P256DerSig::from_cbor(sig_rem).map(|success| {
   3065                                                 CborSuccess {
   3066                                                     value: Sig::P256(success.value.0),
   3067                                                     remaining: success.remaining,
   3068                                                 }
   3069                                             })
   3070                                         } else {
   3071                                             Err(AttestationErr::PackedFormatMissingSig)
   3072                                         }
   3073                                     }),
   3074                                 cbor::NEG_INFO_24 => cose_rem
   3075                                     .split_first()
   3076                                     .ok_or(AttestationErr::Len)
   3077                                     .and_then(|(len, len_rem)| match *len {
   3078                                         ES384 => len_rem
   3079                                             .split_at_checked(SIG.len())
   3080                                             .ok_or(AttestationErr::Len)
   3081                                             .and_then(|(sig, sig_rem)| {
   3082                                                 if sig == SIG {
   3083                                                     P384DerSig::from_cbor(sig_rem).map(|success| {
   3084                                                         CborSuccess {
   3085                                                             value: Sig::P384(success.value.0),
   3086                                                             remaining: success.remaining,
   3087                                                         }
   3088                                                     })
   3089                                                 } else {
   3090                                                     Err(AttestationErr::PackedFormatMissingSig)
   3091                                                 }
   3092                                             }),
   3093                                         MLDSA44 => len_rem
   3094                                             .split_at_checked(SIG.len())
   3095                                             .ok_or(AttestationErr::Len)
   3096                                             .and_then(|(sig, sig_rem)| {
   3097                                                 if sig == SIG {
   3098                                                     MlDsa44Signature::from_cbor(sig_rem).map(
   3099                                                         |success| CborSuccess {
   3100                                                             value: Sig::MlDsa44(success.value.0),
   3101                                                             remaining: success.remaining,
   3102                                                         },
   3103                                                     )
   3104                                                 } else {
   3105                                                     Err(AttestationErr::PackedFormatMissingSig)
   3106                                                 }
   3107                                             }),
   3108                                         MLDSA65 => len_rem
   3109                                             .split_at_checked(SIG.len())
   3110                                             .ok_or(AttestationErr::Len)
   3111                                             .and_then(|(sig, sig_rem)| {
   3112                                                 if sig == SIG {
   3113                                                     MlDsa65Signature::from_cbor(sig_rem).map(
   3114                                                         |success| CborSuccess {
   3115                                                             value: Sig::MlDsa65(success.value.0),
   3116                                                             remaining: success.remaining,
   3117                                                         },
   3118                                                     )
   3119                                                 } else {
   3120                                                     Err(AttestationErr::PackedFormatMissingSig)
   3121                                                 }
   3122                                             }),
   3123                                         MLDSA87 => len_rem
   3124                                             .split_at_checked(SIG.len())
   3125                                             .ok_or(AttestationErr::Len)
   3126                                             .and_then(|(sig, sig_rem)| {
   3127                                                 if sig == SIG {
   3128                                                     MlDsa87Signature::from_cbor(sig_rem).map(
   3129                                                         |success| CborSuccess {
   3130                                                             value: Sig::MlDsa87(success.value.0),
   3131                                                             remaining: success.remaining,
   3132                                                         },
   3133                                                     )
   3134                                                 } else {
   3135                                                     Err(AttestationErr::PackedFormatMissingSig)
   3136                                                 }
   3137                                             }),
   3138                                         _ => Err(AttestationErr::PackedFormatUnsupportedAlg),
   3139                                     }),
   3140                                 cbor::NEG_INFO_25 => cose_rem
   3141                                     .split_at_checked(2)
   3142                                     .ok_or(AttestationErr::Len)
   3143                                     .and_then(|(len, len_rem)| {
   3144                                         if len == RS256 {
   3145                                             len_rem
   3146                                                 .split_at_checked(SIG.len())
   3147                                                 .ok_or(AttestationErr::Len)
   3148                                                 .and_then(|(sig, sig_rem)| {
   3149                                                     if sig == SIG {
   3150                                                         RsaPkcs1v15Sig::from_cbor(sig_rem).map(
   3151                                                             |success| CborSuccess {
   3152                                                                 value: Sig::Rs256(success.value.0),
   3153                                                                 remaining: success.remaining,
   3154                                                             },
   3155                                                         )
   3156                                                     } else {
   3157                                                         Err(AttestationErr::PackedFormatMissingSig)
   3158                                                     }
   3159                                                 })
   3160                                         } else {
   3161                                             Err(AttestationErr::PackedFormatUnsupportedAlg)
   3162                                         }
   3163                                     }),
   3164                                 _ => Err(AttestationErr::PackedFormatUnsupportedAlg),
   3165                             },
   3166                         )
   3167                     } else {
   3168                         Err(AttestationErr::PackedFormatMissingAlg)
   3169                     }
   3170                 })
   3171         }
   3172         cbor.split_first()
   3173             .ok_or(AttestationErr::Len)
   3174             .and_then(|(map, map_rem)| match *map {
   3175                 cbor::MAP_2 => parse_to_cert_chain(map_rem).map(|success| CborSuccess {
   3176                     value: Self {
   3177                         signature: success.value,
   3178                     },
   3179                     remaining: success.remaining,
   3180                 }),
   3181                 _ => Err(AttestationErr::PackedFormat),
   3182             })
   3183     }
   3184 }
   3185 /// [Attestation statement format identifiers](https://www.w3.org/TR/webauthn-3/#sctn-attstn-fmt-ids).
   3186 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   3187 pub enum AttestationFormat<'a> {
   3188     /// [None](https://www.w3.org/TR/webauthn-3/#sctn-none-attestation).
   3189     None,
   3190     /// [Packed](https://www.w3.org/TR/webauthn-3/#sctn-packed-attestation).
   3191     Packed(PackedAttestation<'a>),
   3192 }
   3193 impl<'a> FromCbor<'a> for AttestationFormat<'a> {
   3194     type Err = AttestationErr;
   3195     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   3196         // Note we assume that cbor starts _after_ `cbor::MAP_3`.
   3197         // {"fmt":"none"|"packed", "attStmt": NoneAttestation|CborPacked, ...}.
   3198         /// "fmt" key.
   3199         const FMT: [u8; 4] = [cbor::TEXT_3, b'f', b'm', b't'];
   3200         /// "none" value.
   3201         const NONE: [u8; 5] = [cbor::TEXT_4, b'n', b'o', b'n', b'e'];
   3202         /// "packed" value.
   3203         const PACKED: [u8; 7] = [cbor::TEXT_6, b'p', b'a', b'c', b'k', b'e', b'd'];
   3204         /// "attStmt" key.
   3205         const ATT_STMT: [u8; 8] = [cbor::TEXT_7, b'a', b't', b't', b'S', b't', b'm', b't'];
   3206         cbor.split_at_checked(FMT.len())
   3207             .ok_or(AttestationErr::Len)
   3208             .and_then(|(fmt, fmt_rem)| {
   3209                 if fmt == FMT {
   3210                     fmt_rem
   3211                         .split_at_checked(NONE.len())
   3212                         .ok_or(AttestationErr::Len)
   3213                         // `PACKED.len() > NONE.len()`, so we check for `PACKED` in `and_then`.
   3214                         .and_then(|(none, none_rem)| {
   3215                             if none == NONE {
   3216                                 none_rem
   3217                                     .split_at_checked(ATT_STMT.len())
   3218                                     .ok_or(AttestationErr::Len)
   3219                                     .and_then(|(att, att_rem)| {
   3220                                         if att == ATT_STMT {
   3221                                             NoneAttestation::from_cbor(att_rem).map(|success| {
   3222                                                 CborSuccess {
   3223                                                     value: Self::None,
   3224                                                     remaining: success.remaining,
   3225                                                 }
   3226                                             })
   3227                                         } else {
   3228                                             Err(AttestationErr::MissingStatement)
   3229                                         }
   3230                                     })
   3231                             } else {
   3232                                 fmt_rem
   3233                                     .split_at_checked(PACKED.len())
   3234                                     .ok_or(AttestationErr::Len)
   3235                                     .and_then(|(packed, packed_rem)| {
   3236                                         if packed == PACKED {
   3237                                             packed_rem
   3238                                                 .split_at_checked(ATT_STMT.len())
   3239                                                 .ok_or(AttestationErr::Len)
   3240                                                 .and_then(|(att, att_rem)| {
   3241                                                     if att == ATT_STMT {
   3242                                                         PackedAttestation::from_cbor(att_rem).map(
   3243                                                             |success| CborSuccess {
   3244                                                                 value: Self::Packed(success.value),
   3245                                                                 remaining: success.remaining,
   3246                                                             },
   3247                                                         )
   3248                                                     } else {
   3249                                                         Err(AttestationErr::MissingStatement)
   3250                                                     }
   3251                                                 })
   3252                                         } else {
   3253                                             Err(AttestationErr::UnsupportedFormat)
   3254                                         }
   3255                                     })
   3256                             }
   3257                         })
   3258                 } else {
   3259                     Err(AttestationErr::MissingFormat)
   3260                 }
   3261             })
   3262     }
   3263 }
   3264 impl<'a> AttestationObject<'a> {
   3265     /// [Attestation statement format identifiers](https://www.w3.org/TR/webauthn-3/#sctn-attstn-fmt-ids).
   3266     #[inline]
   3267     #[must_use]
   3268     pub const fn attestation(&self) -> AttestationFormat<'a> {
   3269         self.attestation
   3270     }
   3271     /// [Authenticator data](https://www.w3.org/TR/webauthn-3/#authenticator-data).
   3272     #[inline]
   3273     #[must_use]
   3274     pub const fn auth_data(&self) -> &AuthenticatorData<'a> {
   3275         &self.auth_data
   3276     }
   3277     /// Deserializes `data` based on the
   3278     /// [attestation object layout](https://www.w3.org/TR/webauthn-3/#attestation-object)
   3279     /// returning [`Self`] and the index within `data` that the authenticator data portion
   3280     /// begins.
   3281     #[expect(single_use_lifetimes, reason = "false positive")]
   3282     #[expect(
   3283         clippy::panic_in_result_fn,
   3284         reason = "we want to crash when there is a bug"
   3285     )]
   3286     #[expect(
   3287         clippy::arithmetic_side_effects,
   3288         reason = "comment justifies its correctness"
   3289     )]
   3290     #[expect(clippy::big_endian_bytes, reason = "cbor lengths are in big-endian")]
   3291     fn parse_data<'b: 'a>(data: &'b [u8]) -> Result<(Self, usize), AttestationObjectErr> {
   3292         /// `authData` key.
   3293         const AUTH_DATA_KEY: [u8; 9] =
   3294             [cbor::TEXT_8, b'a', b'u', b't', b'h', b'D', b'a', b't', b'a'];
   3295         // {"fmt":<AttestationFormat>, "attStmt":<AttestationObject>, "authData":<AuthentcatorData>}.
   3296         data.split_first().ok_or(AttestationObjectErr::Len).and_then(|(map, map_rem)| {
   3297             if *map == cbor::MAP_3 {
   3298                 AttestationFormat::from_cbor(map_rem).map_err(AttestationObjectErr::Attestation).and_then(|att| {
   3299                     att.remaining.split_at_checked(AUTH_DATA_KEY.len()).ok_or(AttestationObjectErr::Len).and_then(|(key, key_rem)| {
   3300                         if key == AUTH_DATA_KEY {
   3301                             key_rem.split_first().ok_or(AttestationObjectErr::Len).and_then(|(bytes, bytes_rem)| {
   3302                                 if bytes & cbor::BYTES == cbor::BYTES {
   3303                                     match bytes ^ cbor::BYTES {
   3304                                         24 => bytes_rem.split_first().ok_or(AttestationObjectErr::Len).and_then(|(&len, auth_data)| {
   3305                                             if len > 23 {
   3306                                                 Ok((usize::from(len), auth_data))
   3307                                             } else {
   3308                                                 Err(AttestationObjectErr::AuthDataLenInfo)
   3309                                             }
   3310                                         }),
   3311                                         25 => bytes_rem.split_at_checked(2).ok_or(AttestationObjectErr::Len).and_then(|(len_slice, auth_data)| {
   3312                                             let mut auth_len = [0; 2];
   3313                                             auth_len.copy_from_slice(len_slice);
   3314                                             // Length is encoded as big-endian.
   3315                                             let len = usize::from(u16::from_be_bytes(auth_len));
   3316                                             if len > 255 {
   3317                                                 Ok((len, auth_data))
   3318                                             } else {
   3319                                                 Err(AttestationObjectErr::AuthDataLenInfo)
   3320                                             }
   3321                                         }),
   3322                                         _ => Err(AttestationObjectErr::AuthDataLenInfo),
   3323                                     }.and_then(|(cbor_auth_data_len, auth_slice)| {
   3324                                         if cbor_auth_data_len == auth_slice.len() {
   3325                                             AuthenticatorData::from_cbor(auth_slice).map_err(|e| AttestationObjectErr::AuthData(e.into())).and_then(|auth_data| {
   3326                                                 assert!(auth_data.remaining.is_empty(), "there is a bug in AuthenticatorData::from_cbor");
   3327                                                 match att.value {
   3328                                                     AttestationFormat::None => Ok(()),
   3329                                                     AttestationFormat::Packed(ref val) => match val.signature {
   3330                                                         Sig::MlDsa87(_) => if matches!(auth_data.value.attested_credential_data.credential_public_key, UncompressedPubKey::MlDsa87(_)) {
   3331                                                             Ok(())
   3332                                                         } else {
   3333                                                             Err(AttestationObjectErr::SelfAttestationAlgorithmMismatch)
   3334                                                         },
   3335                                                         Sig::MlDsa65(_) => if matches!(auth_data.value.attested_credential_data.credential_public_key, UncompressedPubKey::MlDsa65(_)) {
   3336                                                             Ok(())
   3337                                                         } else {
   3338                                                             Err(AttestationObjectErr::SelfAttestationAlgorithmMismatch)
   3339                                                         },
   3340                                                         Sig::MlDsa44(_) => if matches!(auth_data.value.attested_credential_data.credential_public_key, UncompressedPubKey::MlDsa44(_)) {
   3341                                                             Ok(())
   3342                                                         } else {
   3343                                                             Err(AttestationObjectErr::SelfAttestationAlgorithmMismatch)
   3344                                                         },
   3345                                                         Sig::Ed25519(_) => if matches!(auth_data.value.attested_credential_data.credential_public_key, UncompressedPubKey::Ed25519(_)) {
   3346                                                             Ok(())
   3347                                                         } else {
   3348                                                             Err(AttestationObjectErr::SelfAttestationAlgorithmMismatch)
   3349                                                         },
   3350                                                         Sig::P256(_) => if matches!(auth_data.value.attested_credential_data.credential_public_key, UncompressedPubKey::P256(_)) {
   3351                                                             Ok(())
   3352                                                         } else {
   3353                                                             Err(AttestationObjectErr::SelfAttestationAlgorithmMismatch)
   3354                                                         },
   3355                                                         Sig::P384(_) => if matches!(auth_data.value.attested_credential_data.credential_public_key, UncompressedPubKey::P384(_)) {
   3356                                                             Ok(())
   3357                                                         } else {
   3358                                                             Err(AttestationObjectErr::SelfAttestationAlgorithmMismatch)
   3359                                                         },
   3360                                                         Sig::Rs256(_) => if matches!(auth_data.value.attested_credential_data.credential_public_key, UncompressedPubKey::Rsa(_)) {
   3361                                                             Ok(())
   3362                                                         } else {
   3363                                                             Err(AttestationObjectErr::SelfAttestationAlgorithmMismatch)
   3364                                                         },
   3365                                                     },
   3366                                                     // `cbor_auth_data_len == `auth_slice.len()` and `auth_slice.len() < data.len()`, so underflow won't happen.
   3367                                                 }.map(|()| (Self { attestation: att.value, auth_data: auth_data.value }, data.len() - cbor_auth_data_len))
   3368                                             })
   3369                                         } else {
   3370                                             Err(AttestationObjectErr::CborAuthDataLenMismatch)
   3371                                         }
   3372                                     })
   3373                                 } else {
   3374                                     Err(AttestationObjectErr::AuthDataType)
   3375                                 }
   3376                             })
   3377                         } else {
   3378                             Err(AttestationObjectErr::MissingAuthData)
   3379                         }
   3380                     })
   3381                 })
   3382             } else {
   3383                 Err(AttestationObjectErr::NotAMapOf3)
   3384             }
   3385         })
   3386     }
   3387 }
   3388 /// [Attestation object](https://www.w3.org/TR/webauthn-3/#attestation-object).
   3389 #[derive(Debug)]
   3390 pub struct AttestationObject<'a> {
   3391     /// [Attestation statement format identifiers](https://www.w3.org/TR/webauthn-3/#sctn-attstn-fmt-ids).
   3392     attestation: AttestationFormat<'a>,
   3393     /// [Authenticator data](https://www.w3.org/TR/webauthn-3/#authenticator-data).
   3394     auth_data: AuthenticatorData<'a>,
   3395 }
   3396 impl<'a: 'b, 'b> TryFrom<&'a [u8]> for AttestationObject<'b> {
   3397     type Error = AttestationObjectErr;
   3398     /// Deserializes `value` based on the
   3399     /// [attestation object layout](https://www.w3.org/TR/webauthn-3/#attestation-object).
   3400     #[inline]
   3401     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
   3402         Self::parse_data(value).map(|(val, _)| val)
   3403     }
   3404 }
   3405 impl<'a> AuthDataContainer<'a> for AttestationObject<'a> {
   3406     type Auth = AuthenticatorData<'a>;
   3407     type Err = AttestationObjectErr;
   3408     #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")]
   3409     #[expect(clippy::indexing_slicing, reason = "comment justifies its correctness")]
   3410     fn from_data(data: &'a [u8]) -> Result<ParsedAuthData<'a, Self>, Self::Err> {
   3411         // `data.len().checked_sub(Sha256::output_size())` is clearly less than `data.len()`;
   3412         // thus indexing wont `panic`.
   3413         Self::parse_data(&data[..data.len().checked_sub(Sha256::output_size()).unwrap_or_else(|| unreachable!("AttestationObject::from_data must be passed a slice with 32 bytes of trailing data"))]).map(|(attest, auth_idx)| ParsedAuthData { data: attest, auth_data_and_32_trailing_bytes: &data[auth_idx..], })
   3414     }
   3415     fn authenticator_data(&self) -> &Self::Auth {
   3416         &self.auth_data
   3417     }
   3418 }
   3419 /// [`AuthenticatorAttestationResponse`](https://www.w3.org/TR/webauthn-3/#authenticatorattestationresponse).
   3420 #[derive(Debug)]
   3421 pub struct AuthenticatorAttestation {
   3422     /// [`clientDataJSON`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorresponse-clientdatajson).
   3423     client_data_json: Vec<u8>,
   3424     /// [attestation object](https://www.w3.org/TR/webauthn-3/#attestation-object) followed by the SHA-256 hash
   3425     /// of [`Self::client_data_json`].
   3426     attestation_object_and_c_data_hash: Vec<u8>,
   3427     /// [`getTransports`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponse-gettransports).
   3428     transports: AuthTransports,
   3429 }
   3430 impl AuthenticatorAttestation {
   3431     /// [`clientDataJSON`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorresponse-clientdatajson).
   3432     #[inline]
   3433     #[must_use]
   3434     pub const fn client_data_json(&self) -> &[u8] {
   3435         self.client_data_json.as_slice()
   3436     }
   3437     /// [attestation object](https://www.w3.org/TR/webauthn-3/#attestation-object).
   3438     #[expect(
   3439         clippy::arithmetic_side_effects,
   3440         clippy::indexing_slicing,
   3441         reason = "comment justifies their correctness"
   3442     )]
   3443     #[inline]
   3444     #[must_use]
   3445     pub fn attestation_object(&self) -> &[u8] {
   3446         // We only allow creation via [`Self::new`] which creates [`Self::attestation_object_and_c_data_hash`]
   3447         // by appending the SHA-256 hash of [`Self::client_data_json`] to the attestation object that was passed;
   3448         // thus indexing is fine and subtraction won't cause underflow.
   3449         &self.attestation_object_and_c_data_hash
   3450             [..self.attestation_object_and_c_data_hash.len() - Sha256::output_size()]
   3451     }
   3452     /// [`getTransports`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponse-gettransports).
   3453     #[inline]
   3454     #[must_use]
   3455     pub const fn transports(&self) -> AuthTransports {
   3456         self.transports
   3457     }
   3458     /// Constructs an instance of `Self` with the contained data.
   3459     ///
   3460     /// Note calling code is encouraged to ensure `attestation_object` has at least 32 bytes
   3461     /// of available capacity; if not, a reallocation will occur which may hinder performance
   3462     /// depending on its size.
   3463     #[inline]
   3464     #[must_use]
   3465     pub fn new(
   3466         client_data_json: Vec<u8>,
   3467         mut attestation_object: Vec<u8>,
   3468         transports: AuthTransports,
   3469     ) -> Self {
   3470         attestation_object.extend_from_slice(&Sha256::digest(client_data_json.as_slice()));
   3471         Self {
   3472             client_data_json,
   3473             attestation_object_and_c_data_hash: attestation_object,
   3474             transports,
   3475         }
   3476     }
   3477 }
   3478 impl AuthResponse for AuthenticatorAttestation {
   3479     type Auth<'a>
   3480         = AttestationObject<'a>
   3481     where
   3482         Self: 'a;
   3483     type CredKey<'a> = ();
   3484     #[expect(clippy::unreachable, reason = "when there is a bug, we want to crash")]
   3485     fn parse_data_and_verify_sig(
   3486         &self,
   3487         (): Self::CredKey<'_>,
   3488         relaxed: bool,
   3489     ) -> Result<
   3490         (CollectedClientData<'_>, Self::Auth<'_>),
   3491         AuthRespErr<<Self::Auth<'_> as AuthDataContainer<'_>>::Err>,
   3492     > {
   3493         if relaxed {
   3494             #[cfg(not(feature = "serde_relaxed"))]
   3495             unreachable!("AuthenticatorAttestation::parse_data_and_verify_sig: must be passed false when serde_relaxed is not enabled");
   3496             #[cfg(feature = "serde_relaxed")]
   3497             CollectedClientData::from_client_data_json_relaxed::<true>(
   3498                 self.client_data_json.as_slice(),
   3499             ).map_err(AuthRespErr::CollectedClientDataRelaxed)
   3500         } else {
   3501             CollectedClientData::from_client_data_json::<true>(self.client_data_json.as_slice()).map_err(AuthRespErr::CollectedClientData)
   3502         }
   3503         .and_then(|client_data_json| {
   3504             Self::Auth::from_data(self.attestation_object_and_c_data_hash.as_slice())
   3505                 .map_err(AuthRespErr::Auth)
   3506                 .and_then(|val| {
   3507                     match val.data.auth_data.attested_credential_data.credential_public_key {
   3508                         UncompressedPubKey::MlDsa87(key) => {
   3509                             match val.data.attestation {
   3510                                 AttestationFormat::None => Ok(()),
   3511                                 AttestationFormat::Packed(packed) => match packed.signature {
   3512                                     Sig::MlDsa87(sig) => MlDsaSignature::<MlDsa87>::decode(sig.as_array().unwrap_or_else(|| unreachable!("there is a bug in slice::as_array")).into()).ok_or(AuthRespErr::Signature).and_then(|s| key.into_ver_key().verify(val.auth_data_and_32_trailing_bytes, &s).map_err(|_e| AuthRespErr::Signature)),
   3513                                     Sig::MlDsa65(_) | Sig::MlDsa44(_) | Sig::Ed25519(_) | Sig::P256(_) | Sig::P384(_) | Sig::Rs256(_) => unreachable!("there is a bug in AttestationObject::from_data"),
   3514                                 }
   3515                             }
   3516                         }
   3517                         UncompressedPubKey::MlDsa65(key) => {
   3518                             match val.data.attestation {
   3519                                 AttestationFormat::None => Ok(()),
   3520                                 AttestationFormat::Packed(packed) => match packed.signature {
   3521                                     Sig::MlDsa65(sig) => MlDsaSignature::<MlDsa65>::decode(sig.as_array().unwrap_or_else(|| unreachable!("there is a bug in slice::as_array")).into()).ok_or(AuthRespErr::Signature).and_then(|s| key.into_ver_key().verify(val.auth_data_and_32_trailing_bytes, &s).map_err(|_e| AuthRespErr::Signature)),
   3522                                     Sig::MlDsa87(_) | Sig::MlDsa44(_) | Sig::Ed25519(_) | Sig::P256(_) | Sig::P384(_) | Sig::Rs256(_) => unreachable!("there is a bug in AttestationObject::from_data"),
   3523                                 }
   3524                             }
   3525                         }
   3526                         UncompressedPubKey::MlDsa44(key) => {
   3527                             match val.data.attestation {
   3528                                 AttestationFormat::None => Ok(()),
   3529                                 AttestationFormat::Packed(packed) => match packed.signature {
   3530                                     Sig::MlDsa44(sig) => MlDsaSignature::<MlDsa44>::decode(sig.as_array().unwrap_or_else(|| unreachable!("there is a bug in slice::as_array")).into()).ok_or(AuthRespErr::Signature).and_then(|s| key.into_ver_key().verify(val.auth_data_and_32_trailing_bytes, &s).map_err(|_e| AuthRespErr::Signature)),
   3531                                     Sig::MlDsa87(_) | Sig::MlDsa65(_) | Sig::Ed25519(_) | Sig::P256(_) | Sig::P384(_) | Sig::Rs256(_) => unreachable!("there is a bug in AttestationObject::from_data"),
   3532                                 }
   3533                             }
   3534                         }
   3535                         UncompressedPubKey::Ed25519(key) => key.into_ver_key().map_err(AuthRespErr::PubKey).and_then(|ver_key| {
   3536                             match val.data.attestation {
   3537                                 AttestationFormat::None => Ok(()),
   3538                                 AttestationFormat::Packed(packed) => match packed.signature {
   3539                                     // We don't need to use `VerifyingKey::verify_strict` since
   3540                                     // `Ed25519PubKey::into_ver_key` verifies the public key is not
   3541                                     // in the small-order subgroup. `VerifyingKey::verify_strict` additionally
   3542                                     // ensures _R_ of the signature is not in the small-order subgroup, but this
   3543                                     // doesn't provide additional benefits and is still not enough to comply
   3544                                     // with standards like RFC 8032 or NIST SP 800-186.
   3545                                     Sig::Ed25519(sig) => ver_key.verify(val.auth_data_and_32_trailing_bytes, &sig.into_sig()).map_err(|_e| AuthRespErr::Signature),
   3546                                     Sig::MlDsa87(_) | Sig::MlDsa65(_) | Sig::MlDsa44(_) | Sig::P256(_) | Sig::P384(_) | Sig::Rs256(_) => unreachable!("there is a bug in AttestationObject::from_data"),
   3547                                 }
   3548                             }
   3549                         }),
   3550                         UncompressedPubKey::P256(key) => key.into_ver_key().map_err(AuthRespErr::PubKey).and_then(|ver_key| {
   3551                             match val.data.attestation {
   3552                                 AttestationFormat::None => Ok(()),
   3553                                 AttestationFormat::Packed(packed) => match packed.signature {
   3554                                     Sig::P256(sig) => P256Sig::from_bytes(sig).map_err(|_e| AuthRespErr::Signature).and_then(|s| ver_key.verify(val.auth_data_and_32_trailing_bytes, &s).map_err(|_e| AuthRespErr::Signature)),
   3555                                     Sig::MlDsa87(_) | Sig::MlDsa65(_) | Sig::MlDsa44(_) | Sig::Ed25519(_) | Sig::P384(_) | Sig::Rs256(_) => unreachable!("there is a bug in AttestationObject::from_data"),
   3556                                 }
   3557                             }
   3558                         }),
   3559                         UncompressedPubKey::P384(key) => key.into_ver_key().map_err(AuthRespErr::PubKey).and_then(|ver_key| {
   3560                             match val.data.attestation {
   3561                                 AttestationFormat::None => Ok(()),
   3562                                 AttestationFormat::Packed(packed) => match packed.signature {
   3563                                     Sig::P384(sig) => P384Sig::from_bytes(sig).map_err(|_e| AuthRespErr::Signature).and_then(|s| ver_key.verify(val.auth_data_and_32_trailing_bytes, &s).map_err(|_e| AuthRespErr::Signature)),
   3564                                     Sig::MlDsa87(_) | Sig::MlDsa65(_) | Sig::MlDsa44(_) | Sig::Ed25519(_) | Sig::P256(_) | Sig::Rs256(_) => unreachable!("there is a bug in AttestationObject::from_data"),
   3565                                 }
   3566                             }
   3567                         }),
   3568                         UncompressedPubKey::Rsa(key) => match val.data.attestation {
   3569                             AttestationFormat::None => Ok(()),
   3570                             AttestationFormat::Packed(packed) => match packed.signature {
   3571                                 Sig::Rs256(sig) => pkcs1v15::Signature::try_from(sig).map_err(|_e| AuthRespErr::Signature).and_then(|s| key.as_ver_key().verify(val.auth_data_and_32_trailing_bytes, &s).map_err(|_e| AuthRespErr::Signature)),
   3572                                 Sig::MlDsa87(_) | Sig::MlDsa65(_) | Sig::MlDsa44(_) | Sig::Ed25519(_) | Sig::P256(_) | Sig::P384(_) => unreachable!("there is a bug in AttestationObject::from_data"),
   3573                             }
   3574                         },
   3575                     }.map(|()| (client_data_json, val.data))
   3576                 })
   3577         })
   3578     }
   3579 }
   3580 /// [`CredentialPropertiesOutput`](https://www.w3.org/TR/webauthn-3/#dictdef-credentialpropertiesoutput).
   3581 ///
   3582 /// Note [`Self::rk`] is frequently unreliable. For example there are times it is `Some(false)` despite the
   3583 /// credential being stored client-side. One may have better luck checking if [`AuthTransports::contains`]
   3584 /// [`AuthenticatorTransport::Internal`] and using that as an indicator if a client-side credential was created.
   3585 #[derive(Clone, Copy, Debug)]
   3586 pub struct CredentialPropertiesOutput {
   3587     /// [`rk`](https://www.w3.org/TR/webauthn-3/#dom-credentialpropertiesoutput-rk).
   3588     pub rk: Option<bool>,
   3589 }
   3590 /// [`AuthenticationExtensionsPRFOutputs`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfoutputs).
   3591 ///
   3592 /// Note since this is a server-side library, we don't store
   3593 /// [`results`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-results)
   3594 /// since it contains sensitive data that should remain client-side.
   3595 #[derive(Clone, Copy, Debug)]
   3596 pub struct AuthenticationExtensionsPrfOutputs {
   3597     /// [`enabled`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-enabled).
   3598     pub enabled: bool,
   3599 }
   3600 /// [`AuthenticationExtensionsClientOutputs`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsclientoutputs).
   3601 #[derive(Clone, Copy, Debug)]
   3602 pub struct ClientExtensionsOutputs {
   3603     /// [`credProps`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-credprops).
   3604     pub cred_props: Option<CredentialPropertiesOutput>,
   3605     /// [`prf`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-prf).
   3606     pub prf: Option<AuthenticationExtensionsPrfOutputs>,
   3607 }
   3608 /// [`ClientExtensionsOutputs`] extensions that are saved in [`Metadata`] because they are purely informative
   3609 /// and not used during authentication ceremonies.
   3610 #[derive(Clone, Copy, Debug)]
   3611 pub struct ClientExtensionsOutputsMetadata {
   3612     /// [`ClientExtensionsOutputs::cred_props`].
   3613     pub cred_props: Option<CredentialPropertiesOutput>,
   3614 }
   3615 /// [`ClientExtensionsOutputs`] extensions that are saved in [`StaticState`] because they are used during
   3616 /// authentication ceremonies.
   3617 #[derive(Clone, Copy, Debug)]
   3618 pub struct ClientExtensionsOutputsStaticState {
   3619     /// [`ClientExtensionsOutputs::prf`].
   3620     pub prf: Option<AuthenticationExtensionsPrfOutputs>,
   3621 }
   3622 impl From<ClientExtensionsOutputs> for ClientExtensionsOutputsMetadata {
   3623     #[inline]
   3624     fn from(value: ClientExtensionsOutputs) -> Self {
   3625         Self {
   3626             cred_props: value.cred_props,
   3627         }
   3628     }
   3629 }
   3630 impl From<ClientExtensionsOutputs> for ClientExtensionsOutputsStaticState {
   3631     #[inline]
   3632     fn from(value: ClientExtensionsOutputs) -> Self {
   3633         Self { prf: value.prf }
   3634     }
   3635 }
   3636 /// [`PublicKeyCredential`](https://www.w3.org/TR/webauthn-3/#iface-pkcredential) for registration ceremonies.
   3637 #[expect(
   3638     clippy::field_scoped_visibility_modifiers,
   3639     reason = "no invariants to uphold"
   3640 )]
   3641 #[derive(Debug)]
   3642 pub struct Registration {
   3643     /// [`response`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-response).
   3644     pub(crate) response: AuthenticatorAttestation,
   3645     /// [`authenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-authenticatorattachment).
   3646     pub(crate) authenticator_attachment: AuthenticatorAttachment,
   3647     /// [`getClientExtensionResults()`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-getclientextensionresults).
   3648     pub(crate) client_extension_results: ClientExtensionsOutputs,
   3649 }
   3650 impl Registration {
   3651     /// [`response`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-response).
   3652     #[inline]
   3653     #[must_use]
   3654     pub const fn response(&self) -> &AuthenticatorAttestation {
   3655         &self.response
   3656     }
   3657     /// [`authenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-authenticatorattachment).
   3658     #[inline]
   3659     #[must_use]
   3660     pub const fn authenticator_attachment(&self) -> AuthenticatorAttachment {
   3661         self.authenticator_attachment
   3662     }
   3663     /// [`getClientExtensionResults()`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-getclientextensionresults).
   3664     #[inline]
   3665     #[must_use]
   3666     pub const fn client_extension_results(&self) -> ClientExtensionsOutputs {
   3667         self.client_extension_results
   3668     }
   3669     /// Constructs a `Registration` based on the passed arguments.
   3670     #[cfg(feature = "custom")]
   3671     #[inline]
   3672     #[must_use]
   3673     pub const fn new(
   3674         response: AuthenticatorAttestation,
   3675         authenticator_attachment: AuthenticatorAttachment,
   3676         client_extension_results: ClientExtensionsOutputs,
   3677     ) -> Self {
   3678         Self {
   3679             response,
   3680             authenticator_attachment,
   3681             client_extension_results,
   3682         }
   3683     }
   3684     /// Returns the associated `SentChallenge`.
   3685     ///
   3686     /// This is useful when wanting to extract the corresponding [`RegistrationServerState`] from
   3687     /// an in-memory collection (e.g., [`MaxLenHashSet`]) or storage.
   3688     ///
   3689     /// Note if [`CollectedClientData::from_client_data_json`] returns `Ok`, then this will return
   3690     /// `Ok` containing the same value as [`CollectedClientData::challenge`]; however the converse
   3691     /// is _not_ true. This is because this function parses the minimal amount of data possible.
   3692     ///
   3693     /// # Errors
   3694     ///
   3695     /// Errors iff [`AuthenticatorAttestation::client_data_json`] does not contain a base64url-encoded
   3696     /// [`Challenge`] in the required position.
   3697     #[inline]
   3698     pub fn challenge(&self) -> Result<SentChallenge, CollectedClientDataErr> {
   3699         LimitedVerificationParser::<true>::get_sent_challenge(
   3700             self.response.client_data_json.as_slice(),
   3701         )
   3702     }
   3703     /// Returns the associated `SentChallenge`.
   3704     ///
   3705     /// This is useful when wanting to extract the corresponding [`RegistrationServerState`] from
   3706     /// an in-memory collection (e.g., [`MaxLenHashSet`]) or storage.
   3707     ///
   3708     /// Note if [`CollectedClientData::from_client_data_json_relaxed`] returns `Ok`, then this will return
   3709     /// `Ok` containing the same value as [`CollectedClientData::challenge`]; however the converse
   3710     /// is _not_ true. This is because this function attempts to reduce the amount of data parsed.
   3711     ///
   3712     /// # Errors
   3713     ///
   3714     /// Errors iff [`AuthenticatorAttestation::client_data_json`] is invalid JSON _after_ ignoring
   3715     /// a leading U+FEFF and replacing any sequences of invalid UTF-8 code units with U+FFFD or
   3716     /// [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-collectedclientdata-challenge) does not exist
   3717     /// or is not a base64url-encoded [`Challenge`].
   3718     #[cfg(feature = "serde_relaxed")]
   3719     #[inline]
   3720     pub fn challenge_relaxed(&self) -> Result<SentChallenge, SerdeJsonErr> {
   3721         RelaxedClientDataJsonParser::<true>::get_sent_challenge(
   3722             self.response.client_data_json.as_slice(),
   3723         )
   3724     }
   3725     /// Convenience function for [`RegistrationRelaxed::deserialize`].
   3726     ///
   3727     /// # Errors
   3728     ///
   3729     /// Errors iff [`RegistrationRelaxed::deserialize`] does.
   3730     #[cfg(feature = "serde_relaxed")]
   3731     #[inline]
   3732     pub fn from_json_relaxed(json: &[u8]) -> Result<Self, SerdeJsonErr> {
   3733         serde_json::from_slice::<RegistrationRelaxed>(json).map(|val| val.0)
   3734     }
   3735     /// Convenience function for [`CustomRegistration::deserialize`].
   3736     ///
   3737     /// # Errors
   3738     ///
   3739     /// Errors iff [`CustomRegistration::deserialize`] does.
   3740     #[cfg(feature = "serde_relaxed")]
   3741     #[inline]
   3742     pub fn from_json_custom(json: &[u8]) -> Result<Self, SerdeJsonErr> {
   3743         serde_json::from_slice::<CustomRegistration>(json).map(|val| val.0)
   3744     }
   3745 }
   3746 impl Response for Registration {
   3747     type Auth = AuthenticatorAttestation;
   3748     fn auth(&self) -> &Self::Auth {
   3749         &self.response
   3750     }
   3751 }
   3752 /// [Attestation statement](https://www.w3.org/TR/webauthn-3/#attestation-statement).
   3753 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   3754 pub enum Attestation {
   3755     /// [None](https://www.w3.org/TR/webauthn-3/#none).
   3756     None,
   3757     /// [Self](https://www.w3.org/TR/webauthn-3/#self).
   3758     Surrogate,
   3759 }
   3760 /// Metadata associated with a [`RegisteredCredential`].
   3761 ///
   3762 /// This information exists purely for informative reasons as it is not used in any way during authentication
   3763 /// ceremonies; consequently, one may not want to store this information.
   3764 #[derive(Clone, Copy, Debug)]
   3765 pub struct Metadata<'a> {
   3766     /// [Attestation statement](https://www.w3.org/TR/webauthn-3/#attestation-statement).
   3767     pub attestation: Attestation,
   3768     /// [`aaguid`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata-aaguid).
   3769     pub aaguid: Aaguid<'a>,
   3770     /// [`extensions`](https://www.w3.org/TR/webauthn-3/#authdata-extensions) output during registration that is
   3771     /// never used during authentication ceremonies.
   3772     pub extensions: AuthenticatorExtensionOutputMetadata,
   3773     /// [`getClientExtensionResults`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-getclientextensionresults)
   3774     /// output during registration that is never used during authentication ceremonies.
   3775     pub client_extension_results: ClientExtensionsOutputsMetadata,
   3776     /// `ResidentKeyRequirement` sent during registration.
   3777     pub resident_key: ResidentKeyRequirement,
   3778 }
   3779 impl Metadata<'_> {
   3780     /// Transforms `self` into a JSON object conforming to the following pseudo-schema:
   3781     ///
   3782     /// ```json
   3783     /// // MetadataJSON:
   3784     /// {
   3785     ///   "attestation": "none" | "self",
   3786     ///   "aaguid": "<32-uppercase-hexadecimal digits>",
   3787     ///   "extensions": {
   3788     ///     "min_pin_length": null | <8-bit unsigned integer without leading 0s>
   3789     ///   },
   3790     ///   "client_extension_results": {
   3791     ///     "cred_props": null | CredPropsJSON
   3792     ///   },
   3793     ///   "resident_key": "required" | "discouraged" | "preferred"
   3794     /// }
   3795     /// // CredPropsJSON:
   3796     /// {
   3797     ///   "rk": null | false | true
   3798     /// }
   3799     /// // PrfJSON:
   3800     /// {
   3801     ///   "enabled": false | true
   3802     /// }
   3803     /// ```
   3804     /// where unnecessary whitespace does not exist.
   3805     ///
   3806     /// This primarily exists so that one can store the data in a human-readable way without the need of
   3807     /// bringing the data back into the application. This allows one to read the data using some external
   3808     /// means purely for informative reasons. If one wants the ability to "act" on the data; then one should
   3809     /// [`Metadata::encode`] the data instead, or in addition to, that way it can be [`MetadataOwned::decode`]d.
   3810     ///
   3811     /// # Examples
   3812     ///
   3813     /// ```
   3814     /// # use core::str::FromStr;
   3815     /// # use webauthn_rp::{
   3816     /// #     request::register::{FourToSixtyThree, ResidentKeyRequirement},
   3817     /// #     response::register::{
   3818     /// #         Aaguid, Attestation,
   3819     /// #         AuthenticatorExtensionOutputMetadata, ClientExtensionsOutputsMetadata, CredentialPropertiesOutput,
   3820     /// #         Metadata,
   3821     /// #     },
   3822     /// # };
   3823     /// let metadata = Metadata {
   3824     ///     attestation: Attestation::None,
   3825     ///     aaguid: Aaguid::try_from([15; 16].as_slice())?,
   3826     ///     extensions: AuthenticatorExtensionOutputMetadata {
   3827     ///         min_pin_length: Some(FourToSixtyThree::Sixteen),
   3828     ///     },
   3829     ///     client_extension_results: ClientExtensionsOutputsMetadata {
   3830     ///         cred_props: Some(CredentialPropertiesOutput {
   3831     ///             rk: Some(true),
   3832     ///         }),
   3833     ///     },
   3834     ///     resident_key: ResidentKeyRequirement::Required
   3835     /// };
   3836     /// let json = serde_json::json!({
   3837     ///     "attestation": "none",
   3838     ///     "aaguid": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F",
   3839     ///     "extensions": {
   3840     ///         "min_pin_length": 16
   3841     ///     },
   3842     ///     "client_extension_results": {
   3843     ///         "cred_props": {
   3844     ///             "rk": true
   3845     ///         }
   3846     ///     },
   3847     ///     "resident_key": "required"
   3848     /// });
   3849     /// assert_eq!(metadata.into_json(), json.to_string());
   3850     /// # Ok::<_, webauthn_rp::AggErr>(())
   3851     /// ```
   3852     #[expect(unsafe_code, reason = "comment justifies its correctness and reason")]
   3853     #[expect(
   3854         clippy::arithmetic_side_effects,
   3855         clippy::integer_division,
   3856         clippy::integer_division_remainder_used,
   3857         reason = "comments justify their correctness"
   3858     )]
   3859     #[inline]
   3860     #[must_use]
   3861     pub fn into_json(self) -> String {
   3862         // Maximum capacity needed is not _that_ much larger than the minimum, 173. An example is the
   3863         // following:
   3864         // `{"attestation":"none","aaguid":"00000000000000000000000000000000","extensions":{"min_pin_length":null},"client_extension_results":{"cred_props":{"rk":false},"prf":{"enabled":false}},"resident_key":"discouraged"}`.
   3865         // We use a raw `Vec` instead of a `String` since we need to transform some binary values into ASCII which
   3866         // is easier to do as bytes.
   3867         let mut buffer = Vec::with_capacity(187);
   3868         buffer.extend_from_slice(br#"{"attestation":"#);
   3869         buffer.extend_from_slice(match self.attestation {
   3870             Attestation::None => br#""none","aaguid":""#,
   3871             Attestation::Surrogate => br#""self","aaguid":""#,
   3872         });
   3873         self.aaguid.0.iter().fold((), |(), byt| {
   3874             // Get the first nibble.
   3875             let nib_fst = byt & 0xf0;
   3876             // We simply add the appropriate offset. For decimal digits this means simply adding `b'0'`; but
   3877             // for uppercase hexadecimal, this means adding 55 since `b'A'` is 65.
   3878             // Overflow cannot occur since this maxes at `b'F'`.
   3879             buffer.push(nib_fst + if nib_fst < 0xa { b'0' } else { 55 });
   3880             // Get the second nibble.
   3881             let nib_snd = byt & 0xf;
   3882             // We simply add the appropriate offset. For decimal digits this means simply adding `b'0'`; but
   3883             // for uppercase hexadecimal, this means adding 55 since `b'A'` is 65.
   3884             // Overflow cannot occur since this maxes at `b'F'`.
   3885             buffer.push(nib_snd + if nib_snd < 0xa { b'0' } else { 55 });
   3886         });
   3887         buffer.extend_from_slice(br#"","extensions":{"min_pin_length":"#);
   3888         match self.extensions.min_pin_length {
   3889             None => buffer.extend_from_slice(b"null"),
   3890             Some(pin) => {
   3891                 // Clearly correct.
   3892                 let dig_1 = pin.into_u8() / 10;
   3893                 // Clearly correct.
   3894                 let dig_2 = pin.into_u8() % 10;
   3895                 if dig_1 > 0 {
   3896                     // We simply add the appropriate offset which is `b'0` for decimal digits.
   3897                     // Overflow cannot occur since this maxes at `b'9'`.
   3898                     buffer.push(dig_1 + b'0');
   3899                 }
   3900                 // We simply add the appropriate offset which is `b'0` for decimal digits.
   3901                 // Overflow cannot occur since this maxes at `b'9'`.
   3902                 buffer.push(dig_2 + b'0');
   3903             }
   3904         }
   3905         buffer.extend_from_slice(br#"},"client_extension_results":{"cred_props":"#);
   3906         match self.client_extension_results.cred_props {
   3907             None => buffer.extend_from_slice(b"null"),
   3908             Some(props) => {
   3909                 buffer.extend_from_slice(br#"{"rk":"#);
   3910                 match props.rk {
   3911                     None => buffer.extend_from_slice(b"null}"),
   3912                     Some(rk) => buffer.extend_from_slice(if rk { b"true}" } else { b"false}" }),
   3913                 }
   3914             }
   3915         }
   3916         buffer.extend_from_slice(br#"},"resident_key":"#);
   3917         buffer.extend_from_slice(match self.resident_key {
   3918             ResidentKeyRequirement::Required => br#""required"}"#,
   3919             ResidentKeyRequirement::Discouraged => br#""discouraged"}"#,
   3920             ResidentKeyRequirement::Preferred => br#""preferred"}"#,
   3921         });
   3922         // SAFETY:
   3923         // Clearly above only appends ASCII, a subset of UTF-8, to `buffer`; thus `buffer`
   3924         // is valid UTF-8.
   3925         unsafe { String::from_utf8_unchecked(buffer) }
   3926     }
   3927     /// Transforms `self` into an "owned" version.
   3928     #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")]
   3929     #[cfg(feature = "bin")]
   3930     #[inline]
   3931     #[must_use]
   3932     pub fn into_owned(self) -> MetadataOwned {
   3933         MetadataOwned {
   3934             attestation: self.attestation,
   3935             aaguid: AaguidOwned(self.aaguid.0.try_into().unwrap_or_else(|_e| {
   3936                 unreachable!(
   3937                     "there is a bug in Metadata that allows AAGUID to not have length of 16"
   3938                 )
   3939             })),
   3940             extensions: self.extensions,
   3941             client_extension_results: self.client_extension_results,
   3942             resident_key: self.resident_key,
   3943         }
   3944     }
   3945 }
   3946 /// [`RegisteredCredential`] and [`AuthenticatedCredential`] static state.
   3947 ///
   3948 /// `PublicKey` needs to be [`UncompressedPubKey`] or [`CompressedPubKey`] for this type to be of any use.
   3949 #[derive(Clone, Copy, Debug)]
   3950 pub struct StaticState<PublicKey> {
   3951     /// [`credentialPublicKey`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata-credentialpublickey).
   3952     pub credential_public_key: PublicKey,
   3953     /// [`extensions`](https://www.w3.org/TR/webauthn-3/#authdata-extensions) output during registration that are
   3954     /// used during authentication ceremonies.
   3955     pub extensions: AuthenticatorExtensionOutputStaticState,
   3956     /// [`getClientExtensionResults`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-getclientextensionresults)
   3957     /// output during registration that are used during authentication ceremonies.
   3958     pub client_extension_results: ClientExtensionsOutputsStaticState,
   3959 }
   3960 impl<
   3961     'a: 'b,
   3962     'b,
   3963     T: AsRef<[u8]>,
   3964     T2: AsRef<[u8]>,
   3965     T3: AsRef<[u8]>,
   3966     T4: AsRef<[u8]>,
   3967     T5: AsRef<[u8]>,
   3968     T6: AsRef<[u8]>,
   3969     T7: AsRef<[u8]>,
   3970 > From<&'a StaticState<CompressedPubKey<T, T2, T3, T4, T5, T6, T7>>>
   3971     for StaticState<CompressedPubKeyBorrowed<'b>>
   3972 {
   3973     #[inline]
   3974     fn from(value: &'a StaticState<CompressedPubKey<T, T2, T3, T4, T5, T6, T7>>) -> Self {
   3975         Self {
   3976             credential_public_key: (&value.credential_public_key).into(),
   3977             extensions: value.extensions,
   3978             client_extension_results: value.client_extension_results,
   3979         }
   3980     }
   3981 }
   3982 /// `StaticState` with an uncompressed [`Self::credential_public_key`].
   3983 pub type StaticStateUncompressed<'a> = StaticState<UncompressedPubKey<'a>>;
   3984 /// `StaticState` with a compressed [`Self::credential_public_key`] that owns the key data.
   3985 pub type StaticStateCompressed = StaticState<CompressedPubKeyOwned>;
   3986 impl StaticStateUncompressed<'_> {
   3987     /// Transforms `self` into `StaticState` that contains the compressed version of the public key.
   3988     #[inline]
   3989     #[must_use]
   3990     pub fn into_compressed(self) -> StaticStateCompressed {
   3991         StaticStateCompressed {
   3992             credential_public_key: self.credential_public_key.into_compressed(),
   3993             extensions: self.extensions,
   3994             client_extension_results: self.client_extension_results,
   3995         }
   3996     }
   3997 }
   3998 /// [`RegisteredCredential`] and [`AuthenticatedCredential`] dynamic state.
   3999 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   4000 pub struct DynamicState {
   4001     /// [`UV`](https://www.w3.org/TR/webauthn-3/#authdata-flags-uv).
   4002     ///
   4003     /// Once this is `true`, it will remain `true`. It will be set to `true` when `false` iff
   4004     /// [`Flag::user_verified`] and [`AuthenticationVerificationOptions::update_uv`]. In other words, this only
   4005     /// means that the user has been verified _at some point in the past_; but it does _not_ mean the user has
   4006     /// most-recently been verified this is because user verification is a _ceremony-specific_ property. To
   4007     /// enforce user verification for all ceremonies, [`UserVerificationRequirement::Required`] must always be
   4008     /// sent.
   4009     pub user_verified: bool,
   4010     /// This can only be updated if [`BackupStateReq`] allows for it.
   4011     pub backup: Backup,
   4012     /// [`signCount`](https://www.w3.org/TR/webauthn-3/#authdata-signcount).
   4013     ///
   4014     /// This is only updated if the authenticator supports
   4015     /// [signature counters](https://www.w3.org/TR/webauthn-3/#signature-counter), and the behavior of how it is
   4016     /// updated is controlled by [`AuthenticationVerificationOptions::sig_counter_enforcement`].
   4017     pub sign_count: u32,
   4018     /// [`authenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-authenticatorattachment).
   4019     ///
   4020     /// [`AuthenticationVerificationOptions::auth_attachment_enforcement`] controls if/how this updated.
   4021     pub authenticator_attachment: AuthenticatorAttachment,
   4022 }
   4023 impl PartialEq<&Self> for DynamicState {
   4024     #[inline]
   4025     fn eq(&self, other: &&Self) -> bool {
   4026         *self == **other
   4027     }
   4028 }
   4029 impl PartialEq<DynamicState> for &DynamicState {
   4030     #[inline]
   4031     fn eq(&self, other: &DynamicState) -> bool {
   4032         **self == *other
   4033     }
   4034 }