Skip to main content

didbot_key/
lib.rs

1//! Custodial signing keys for agent accounts.
2//!
3//! This personal data server holds an agent's signing key the way any hosted
4//! atproto PDS holds keys for its users. Agents never hold long-lived key
5//! material; they ask the server to sign, and the server decides whether to.
6//! [`SigningKey::to_bytes`] and [`SigningKey::from_bytes`] exist for that
7//! custody, not for handing keys out.
8//!
9//! # Why secp256k1 and nothing else
10//!
11//! atproto accepts two signing curves, secp256k1 (`0xE7`) and P-256
12//! (`0x1200`), but it requires every ECDSA signature to be *low-S*: of the two
13//! mathematically valid `s` values for a signature, only the one below half
14//! the curve order is accepted. This removes the trivial malleability where
15//! `(r, n - s)` verifies just as well as `(r, s)` and would give the same
16//! record two different valid signatures.
17//!
18//! The `k256` crate enforces that for us in both directions. Its
19//! `SignPrimitive` normalizes `s` before returning a signature, and its
20//! `VerifyPrimitive` rejects a signature whose `s` is high. The `p256` crate
21//! does neither: it emits whichever `s` the math produced, so around half of
22//! the signatures a P-256 implementation would produce here are malleable and
23//! a relay would reject them. Getting P-256 right means normalizing by hand on
24//! every sign and re-checking on every verify, with a silent, intermittent,
25//! half-the-time failure as the cost of forgetting once.
26//!
27//! So this crate *signs* with secp256k1 only, and the low-S property is
28//! covered by a test rather than by this paragraph. It can still **read** a
29//! P-256 key and judge a P-256 signature, which is a different thing: see
30//! [`secp256r1`], where the normalization this crate refuses to do on the way
31//! out is done by hand on the way in. A server that will one day verify
32//! another server's commit needs both curves; nothing here can mint the
33//! second one.
34//!
35//! # Encoding
36//!
37//! Public keys leave this crate as `did:key:z...`, or as the bare multibase
38//! string that goes in a DID document's `publicKeyMultibase`. atproto requires
39//! the **33-byte SEC1 compressed** point encoding there. Nothing in the stack
40//! compresses on your behalf — `k256` will happily hand back a 65-byte
41//! uncompressed point, and `jacquard-common` accepts both on decode — so
42//! [`VerifyingKey::to_multibase`] asks for compression explicitly and
43//! [`VerifyingKey::from_multibase`] refuses anything else.
44//!
45//! ```
46//! use didbot_key::{SigningKey, VerifyingKey};
47//!
48//! let key = SigningKey::generate();
49//! let sig = key.sign(b"record");
50//! assert_eq!(sig.to_bytes().len(), 64);
51//!
52//! let did = key.verifying_key().to_did_key();
53//! assert!(did.starts_with("did:key:z"));
54//! VerifyingKey::from_did_key(&did)?.verify(b"record", &sig)?;
55//! # Ok::<(), didbot_key::KeyError>(())
56//! ```
57
58#![forbid(unsafe_code)]
59
60pub mod secp256r1;
61
62use core::fmt;
63
64use jacquard_common::types::crypto::{multikey, KeyCodec, PublicKey};
65use k256::ecdsa::signature::{Signer, Verifier};
66use k256::elliptic_curve::scalar::IsHigh;
67
68/// Multicodec code for a secp256k1 public key.
69const SECP256K1_CODEC: u64 = 0xE7;
70
71/// URI scheme prefix for the `did:key` method.
72const DID_KEY_PREFIX: &str = "did:key:";
73
74/// Length of a SEC1 compressed secp256k1 point.
75const COMPRESSED_LEN: usize = 33;
76
77/// Anything that can go wrong handling a key or a signature.
78#[derive(Debug, thiserror::Error)]
79#[non_exhaustive]
80pub enum KeyError {
81    /// The 32 bytes are not a valid secp256k1 scalar: zero, or at or above the
82    /// curve order.
83    #[error("not a valid secp256k1 private key")]
84    InvalidPrivateKey,
85
86    /// The point bytes are not on the curve, or are otherwise malformed.
87    #[error("not a valid secp256k1 public key")]
88    InvalidPublicKey,
89
90    /// The string did not start with `did:key:`.
91    #[error("not a did:key URI")]
92    NotDidKey,
93
94    /// The multibase string was not base58btc, which atproto requires. Other
95    /// multibase alphabets decode fine but are not interoperable here.
96    #[error("multibase string is not base58btc (expected a leading 'z')")]
97    NotBase58Btc,
98
99    /// The multibase or multicodec framing did not decode.
100    #[error("could not decode multikey: {0}")]
101    Multikey(#[from] jacquard_common::types::crypto::CryptoError),
102
103    /// The key decoded, but it is for a curve this crate does not sign with.
104    /// See the module docs for why there is only one.
105    #[error("expected a secp256k1 key, got {codec:?}")]
106    WrongCurve {
107        /// The codec that was actually present.
108        codec: KeyCodec,
109    },
110
111    /// A secp256k1 key in the 65-byte uncompressed form, or some other length.
112    #[error("expected a 33-byte compressed key, got {got} bytes")]
113    NotCompressed {
114        /// Length of the key bytes that were present.
115        got: usize,
116    },
117
118    /// The 64 bytes are not a well-formed `r || s` pair.
119    #[error("not a valid compact signature")]
120    InvalidSignature,
121
122    /// The signature is well-formed but does not match this key and message.
123    /// A high-S signature lands here too: `k256` rejects those on verify.
124    #[error("signature did not verify")]
125    VerificationFailed,
126}
127
128/// A secp256k1 ECDSA signature over SHA-256, in 64-byte compact `r || s` form.
129///
130/// Every value of this type that this crate produces is low-S; see the module
131/// docs.
132#[derive(Clone, Copy, PartialEq, Eq)]
133pub struct Signature(k256::ecdsa::Signature);
134
135impl Signature {
136    /// The 64-byte compact `r || s` encoding, which is what atproto puts in a
137    /// record's `sig` field.
138    pub fn to_bytes(&self) -> [u8; 64] {
139        self.0.to_bytes().into()
140    }
141
142    /// Parses a 64-byte compact `r || s` signature.
143    ///
144    /// This accepts a high-S signature — rejecting malleability is
145    /// [`VerifyingKey::verify`]'s job, so that a signature from elsewhere can
146    /// be parsed and then be honestly reported as failing to verify.
147    pub fn from_bytes(bytes: &[u8; 64]) -> Result<Self, KeyError> {
148        k256::ecdsa::Signature::from_slice(bytes)
149            .map(Self)
150            .map_err(|_| KeyError::InvalidSignature)
151    }
152
153    /// Whether `s` is in the lower half of the curve order, as atproto
154    /// requires.
155    pub fn is_low_s(&self) -> bool {
156        !bool::from(self.0.s().is_high())
157    }
158
159    /// The underlying `k256` signature, for callers that need `r`, `s`, or DER.
160    pub fn as_k256(&self) -> &k256::ecdsa::Signature {
161        &self.0
162    }
163}
164
165impl From<k256::ecdsa::Signature> for Signature {
166    fn from(inner: k256::ecdsa::Signature) -> Self {
167        Self(inner)
168    }
169}
170
171impl fmt::Debug for Signature {
172    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173        // Signatures are public, but the default k256 rendering is a wall of
174        // scalar internals. Hex of the wire form is what a log reader wants.
175        f.write_str("Signature(")?;
176        for byte in self.to_bytes() {
177            write!(f, "{byte:02x}")?;
178        }
179        f.write_str(")")
180    }
181}
182
183/// A secp256k1 private key held on behalf of an agent.
184///
185/// The inner `k256` key zeroizes on drop. That is a floor, not a guarantee:
186/// anything that goes through [`SigningKey::to_bytes`] is an ordinary array
187/// and is the caller's problem.
188#[derive(Clone)]
189pub struct SigningKey(k256::ecdsa::SigningKey);
190
191impl SigningKey {
192    /// Generates a new key from the operating system's CSPRNG.
193    pub fn generate() -> Self {
194        Self(k256::ecdsa::SigningKey::random(&mut rand_core::OsRng))
195    }
196
197    /// Loads a key from its 32-byte big-endian scalar, as written by
198    /// [`SigningKey::to_bytes`].
199    ///
200    /// Fails for zero and for anything at or above the curve order, so a
201    /// corrupted or truncated store surfaces here rather than as an unusable
202    /// key later.
203    pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self, KeyError> {
204        k256::ecdsa::SigningKey::from_bytes(bytes.into())
205            .map(Self)
206            .map_err(|_| KeyError::InvalidPrivateKey)
207    }
208
209    /// The 32-byte big-endian scalar, for writing to custodial storage.
210    ///
211    /// This is the whole secret. Nothing else in this crate exposes it.
212    pub fn to_bytes(&self) -> [u8; 32] {
213        self.0.to_bytes().into()
214    }
215
216    /// Signs a message with ECDSA over SHA-256.
217    ///
218    /// The nonce is deterministic (RFC 6979), so the same key and message give
219    /// the same signature, and a bad RNG at signing time cannot leak the key.
220    pub fn sign(&self, message: &[u8]) -> Signature {
221        Signature(self.0.sign(message))
222    }
223
224    /// The public half.
225    pub fn verifying_key(&self) -> VerifyingKey {
226        VerifyingKey(*self.0.verifying_key())
227    }
228}
229
230impl fmt::Debug for SigningKey {
231    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232        // Written by hand so that no derive, now or after a future field is
233        // added, can put the scalar into a log line or a panic message. The
234        // public identifier is enough to tell two keys apart while debugging.
235        f.debug_struct("SigningKey")
236            .field("public", &self.verifying_key().to_did_key())
237            .finish_non_exhaustive()
238    }
239}
240
241/// The public half of a [`SigningKey`].
242#[derive(Clone, Copy, PartialEq, Eq)]
243pub struct VerifyingKey(k256::ecdsa::VerifyingKey);
244
245impl VerifyingKey {
246    /// The `publicKeyMultibase` value for a DID document verification method:
247    /// base58btc multibase over the multicodec prefix `0xE7` and the 33-byte
248    /// SEC1 compressed point.
249    ///
250    /// Compression is requested explicitly here because `k256` can produce
251    /// either form and an uncompressed key is not valid in atproto.
252    pub fn to_multibase(&self) -> String {
253        multikey(SECP256K1_CODEC, self.0.to_encoded_point(true).as_bytes())
254    }
255
256    /// The `did:key` URI form: the `did:key:` prefix plus
257    /// [`VerifyingKey::to_multibase`].
258    pub fn to_did_key(&self) -> String {
259        format!("{DID_KEY_PREFIX}{}", self.to_multibase())
260    }
261
262    /// Parses a `did:key:z...` URI.
263    ///
264    /// `jacquard-common` decodes bare multibase strings, not URIs, so the
265    /// prefix is stripped here.
266    pub fn from_did_key(did: &str) -> Result<Self, KeyError> {
267        let multibase = did
268            .strip_prefix(DID_KEY_PREFIX)
269            .ok_or(KeyError::NotDidKey)?;
270        Self::from_multibase(multibase)
271    }
272
273    /// Parses a bare `publicKeyMultibase` string.
274    ///
275    /// Rejects any curve other than secp256k1 and any encoding other than the
276    /// 33-byte compressed point, so that a key which round-trips through this
277    /// crate is one atproto will accept.
278    pub fn from_multibase(multibase: &str) -> Result<Self, KeyError> {
279        if !multibase.starts_with('z') {
280            return Err(KeyError::NotBase58Btc);
281        }
282        let public = PublicKey::decode_owned(multibase)?;
283        if public.codec != KeyCodec::Secp256k1 {
284            return Err(KeyError::WrongCurve {
285                codec: public.codec,
286            });
287        }
288        if public.bytes.len() != COMPRESSED_LEN {
289            return Err(KeyError::NotCompressed {
290                got: public.bytes.len(),
291            });
292        }
293        let point = public.to_k256().map_err(|_| KeyError::InvalidPublicKey)?;
294        Ok(Self(point.into()))
295    }
296
297    /// Verifies a signature over `message`.
298    ///
299    /// A high-S signature fails here, because `k256` rejects one internally.
300    pub fn verify(&self, message: &[u8], sig: &Signature) -> Result<(), KeyError> {
301        self.0
302            .verify(message, &sig.0)
303            .map_err(|_| KeyError::VerificationFailed)
304    }
305
306    /// The underlying `k256` key, for callers doing their own point work.
307    pub fn as_k256(&self) -> &k256::ecdsa::VerifyingKey {
308        &self.0
309    }
310}
311
312impl fmt::Debug for VerifyingKey {
313    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
314        f.debug_tuple("VerifyingKey")
315            .field(&self.to_did_key())
316            .finish()
317    }
318}
319
320#[cfg(test)]
321mod tests {
322    use super::*;
323
324    use jacquard_common::types::crypto::decode_uvarint;
325
326    #[test]
327    fn sign_and_verify_round_trip() {
328        let key = SigningKey::generate();
329        let message = b"quernstone attestation payload";
330        let sig = key.sign(message);
331        assert!(key.verifying_key().verify(message, &sig).is_ok());
332    }
333
334    #[test]
335    fn signature_does_not_verify_against_another_message() {
336        let key = SigningKey::generate();
337        let sig = key.sign(b"first message");
338        let err = key.verifying_key().verify(b"second message", &sig);
339        assert!(matches!(err, Err(KeyError::VerificationFailed)));
340    }
341
342    #[test]
343    fn signature_does_not_verify_against_another_key() {
344        let message = b"shared message";
345        let sig = SigningKey::generate().sign(message);
346        let other = SigningKey::generate();
347        assert!(matches!(
348            other.verifying_key().verify(message, &sig),
349            Err(KeyError::VerificationFailed)
350        ));
351    }
352
353    #[test]
354    fn did_key_round_trips() {
355        let key = SigningKey::generate().verifying_key();
356        let did = key.to_did_key();
357        assert_eq!(VerifyingKey::from_did_key(&did).expect("parse"), key);
358    }
359
360    #[test]
361    fn multibase_round_trips() {
362        let key = SigningKey::generate().verifying_key();
363        let mb = key.to_multibase();
364        assert_eq!(VerifyingKey::from_multibase(&mb).expect("parse"), key);
365    }
366
367    /// The point of this crate is to hand keys to jacquard. If our encoding and
368    /// its decoder ever disagree, everything downstream silently stops working,
369    /// so agreement is asserted directly rather than assumed.
370    #[test]
371    fn did_key_decodes_through_jacquard() {
372        let key = SigningKey::generate().verifying_key();
373        let did = key.to_did_key();
374        let multibase = did.strip_prefix(DID_KEY_PREFIX).expect("prefix");
375
376        let decoded = PublicKey::decode_owned(multibase).expect("jacquard decode");
377        assert_eq!(decoded.codec, KeyCodec::Secp256k1);
378        assert_eq!(
379            decoded.to_k256().expect("to k256"),
380            k256::PublicKey::from(key.as_k256())
381        );
382    }
383
384    #[test]
385    fn multibase_is_base58btc_compressed_secp256k1() {
386        let mb = SigningKey::generate().verifying_key().to_multibase();
387        assert!(mb.starts_with('z'), "expected base58btc multibase: {mb}");
388
389        let (_base, data) = multibase::decode(&mb).expect("multibase decode");
390        let (code, offset) = decode_uvarint(&data).expect("varint");
391        assert_eq!(code, SECP256K1_CODEC);
392        assert_eq!(data.len() - offset, COMPRESSED_LEN);
393        // 0x02/0x03 is the SEC1 compressed tag; 0x04 would mean 65 uncompressed
394        // bytes, which atproto rejects.
395        assert!(matches!(data[offset], 0x02 | 0x03));
396    }
397
398    /// The single property the curve choice rests on. Deterministic RFC 6979
399    /// nonces mean a fixed set of messages would only ever prove itself, so the
400    /// loop covers a fresh key and many distinct messages.
401    #[test]
402    fn every_signature_is_low_s() {
403        for i in 0..256u32 {
404            let key = SigningKey::generate();
405            let message = format!("message {i}");
406            let sig = key.sign(message.as_bytes());
407            assert!(
408                !bool::from(sig.as_k256().s().is_high()),
409                "high-S signature for message {i}"
410            );
411            assert!(sig.is_low_s());
412        }
413    }
414
415    #[test]
416    fn private_key_bytes_round_trip() {
417        let key = SigningKey::generate();
418        let restored = SigningKey::from_bytes(&key.to_bytes()).expect("reload");
419        assert_eq!(restored.verifying_key(), key.verifying_key());
420    }
421
422    #[test]
423    fn from_bytes_rejects_invalid_scalars() {
424        // Zero is not a valid scalar.
425        assert!(matches!(
426            SigningKey::from_bytes(&[0u8; 32]),
427            Err(KeyError::InvalidPrivateKey)
428        ));
429        // All-ones is above the secp256k1 group order.
430        assert!(matches!(
431            SigningKey::from_bytes(&[0xFFu8; 32]),
432            Err(KeyError::InvalidPrivateKey)
433        ));
434        // Exactly the group order n, the first value that is out of range.
435        let order = [
436            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
437            0xFF, 0xFE, 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 0xBF, 0xD2, 0x5E, 0x8C,
438            0xD0, 0x36, 0x41, 0x41,
439        ];
440        assert!(matches!(
441            SigningKey::from_bytes(&order),
442            Err(KeyError::InvalidPrivateKey)
443        ));
444        // n - 1 is the largest valid scalar, so the bound is not off by one.
445        let mut max = order;
446        max[31] = 0x40;
447        assert!(SigningKey::from_bytes(&max).is_ok());
448    }
449
450    #[test]
451    fn debug_never_prints_key_material() {
452        let key = SigningKey::generate();
453        let rendered = format!("{key:?}");
454
455        // The whole rendering, not a search through it for things that look
456        // like the scalar. A substring search over two arbitrary strings
457        // decides by luck how often it is right — the decimal of a single
458        // secret byte is one to three digits, and a base58 did:key is full of
459        // digits, so that search failed about one run in ten while the code
460        // under it was correct. Pinning the entire output instead is exact:
461        // any leaked byte, any added field, any switch to a derived Debug
462        // changes this string and fails here.
463        let public = key.verifying_key().to_did_key();
464        assert_eq!(rendered, format!("SigningKey {{ public: {public:?}, .. }}"));
465
466        // Spelled out so the property being defended stays legible: the
467        // renderings a derived Debug would have produced are the ones that
468        // must never appear. Each is the full 32-byte scalar, so unlike a
469        // fragment it cannot collide with the public identifier by chance.
470        let secret = key.to_bytes();
471        let lower: String = secret.iter().map(|b| format!("{b:02x}")).collect();
472        let upper: String = secret.iter().map(|b| format!("{b:02X}")).collect();
473        let derived = format!("{secret:?}");
474        for leak in [&lower, &upper, &derived] {
475            assert!(!rendered.contains(leak), "leaked {leak} in {rendered}");
476        }
477    }
478
479    #[test]
480    fn rejects_did_key_with_wrong_prefix() {
481        let mb = SigningKey::generate().verifying_key().to_multibase();
482        assert!(matches!(
483            VerifyingKey::from_did_key(&mb),
484            Err(KeyError::NotDidKey)
485        ));
486        assert!(matches!(
487            VerifyingKey::from_did_key(&format!("did:plc:{mb}")),
488            Err(KeyError::NotDidKey)
489        ));
490        assert!(matches!(
491            VerifyingKey::from_did_key(""),
492            Err(KeyError::NotDidKey)
493        ));
494    }
495
496    #[test]
497    fn rejects_other_curves() {
498        // ed25519: multicodec 0xED over 32 bytes.
499        let ed = format!("{DID_KEY_PREFIX}{}", multikey(0xED, &[7u8; 32]));
500        assert!(matches!(
501            VerifyingKey::from_did_key(&ed),
502            Err(KeyError::WrongCurve {
503                codec: KeyCodec::Ed25519
504            })
505        ));
506
507        // P-256: multicodec 0x1200 over a compressed point. Rejected on codec
508        // alone, before the point is ever looked at; see the module docs.
509        let p256 = {
510            let mut bytes = [0u8; COMPRESSED_LEN];
511            bytes[0] = 0x02;
512            format!("{DID_KEY_PREFIX}{}", multikey(0x1200, &bytes))
513        };
514        assert!(matches!(
515            VerifyingKey::from_did_key(&p256),
516            Err(KeyError::WrongCurve {
517                codec: KeyCodec::P256
518            })
519        ));
520    }
521
522    #[test]
523    fn rejects_unknown_multicodec() {
524        let bogus = format!("{DID_KEY_PREFIX}{}", multikey(0x99, &[2u8; COMPRESSED_LEN]));
525        assert!(matches!(
526            VerifyingKey::from_did_key(&bogus),
527            Err(KeyError::Multikey(_))
528        ));
529    }
530
531    #[test]
532    fn rejects_uncompressed_secp256k1() {
533        let uncompressed = SigningKey::generate()
534            .verifying_key()
535            .as_k256()
536            .to_encoded_point(false);
537        assert_eq!(uncompressed.as_bytes().len(), 65);
538        let did = format!(
539            "{DID_KEY_PREFIX}{}",
540            multikey(SECP256K1_CODEC, uncompressed.as_bytes())
541        );
542        assert!(matches!(
543            VerifyingKey::from_did_key(&did),
544            Err(KeyError::NotCompressed { got: 65 })
545        ));
546    }
547
548    #[test]
549    fn rejects_wrong_length() {
550        let short = format!("{DID_KEY_PREFIX}{}", multikey(SECP256K1_CODEC, &[2u8; 20]));
551        assert!(matches!(
552            VerifyingKey::from_did_key(&short),
553            // jacquard's own length check fires before ours for this one.
554            Err(KeyError::Multikey(_)) | Err(KeyError::NotCompressed { .. })
555        ));
556    }
557
558    #[test]
559    fn rejects_bad_base58() {
560        let mut mb = SigningKey::generate().verifying_key().to_multibase();
561        // 0, O, I and l are not in the base58btc alphabet.
562        mb.push('0');
563        assert!(matches!(
564            VerifyingKey::from_did_key(&format!("{DID_KEY_PREFIX}{mb}")),
565            Err(KeyError::Multikey(_))
566        ));
567    }
568
569    #[test]
570    fn rejects_non_base58btc_multibase() {
571        // A valid multibase string in base64url ('u'), which decodes fine but
572        // is not what atproto specifies.
573        let key = SigningKey::generate().verifying_key();
574        let mut payload = jacquard_common::types::crypto::encode_uvarint(SECP256K1_CODEC);
575        payload.extend_from_slice(key.as_k256().to_encoded_point(true).as_bytes());
576        let b64 = multibase::encode(multibase::Base::Base64Url, &payload);
577        assert!(matches!(
578            VerifyingKey::from_multibase(&b64),
579            Err(KeyError::NotBase58Btc)
580        ));
581    }
582
583    #[test]
584    fn signature_bytes_round_trip() {
585        let key = SigningKey::generate();
586        let message = b"wire form";
587        let sig = key.sign(message);
588        let bytes = sig.to_bytes();
589        assert_eq!(bytes.len(), 64);
590        let parsed = Signature::from_bytes(&bytes).expect("parse");
591        assert_eq!(parsed, sig);
592        assert!(key.verifying_key().verify(message, &parsed).is_ok());
593    }
594
595    #[test]
596    fn all_zero_signature_is_rejected() {
597        assert!(matches!(
598            Signature::from_bytes(&[0u8; 64]),
599            Err(KeyError::InvalidSignature)
600        ));
601    }
602
603    /// Malleability in practice: flipping a valid signature to its high-S twin
604    /// keeps it mathematically valid but must not verify here.
605    #[test]
606    fn high_s_signature_does_not_verify() {
607        let key = SigningKey::generate();
608        let message = b"malleable?";
609        let sig = key.sign(message);
610
611        let (r, s) = sig.as_k256().split_scalars();
612        let flipped = k256::ecdsa::Signature::from_scalars(r.to_bytes(), (-*s).to_bytes())
613            .expect("negated s is a valid scalar");
614        assert!(bool::from(flipped.s().is_high()));
615
616        let flipped = Signature::from(flipped);
617        assert!(!flipped.is_low_s());
618        assert!(matches!(
619            key.verifying_key().verify(message, &flipped),
620            Err(KeyError::VerificationFailed)
621        ));
622    }
623}