webauthn_rp

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

register.rs (190939B)


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