Skip to main content

defuse_crypto/
secp256k1.rs

1//! Secp256k1 (a.k.a. k256) Elliptic Curve Digital Signature Algorithm
2
3pub use k256;
4
5use k256::{
6    Sec1Point,
7    ecdsa::{RecoveryId, Signature, VerifyingKey},
8};
9
10use crate::{Curve, RecoverableCurve};
11
12/// Secp256k1 (a.k.a. k256) Elliptic Curve Digital Signature Algorithm
13pub struct Secp256k1;
14
15impl Curve for Secp256k1 {
16    type PublicKey = VerifyingKey;
17    type Signature = Signature;
18
19    /// Verify secp256k1 signature over **32-byte prehash** (i.e. output of
20    /// cryptographic hash function) for given public key.
21    #[inline]
22    fn verify(public_key: &VerifyingKey, prehash: &[u8], signature: &Self::Signature) -> bool {
23        // accept only 32 byte prehash
24        let Ok(prehash) = <&[u8; 32]>::try_from(prehash) else {
25            return false;
26        };
27
28        cfg_select! {
29            near => {
30                // `near_sdk::env::ecrecover` requires recovery_id, so
31                // we need to find one by trial recovery
32                for id in 0..=RecoveryId::MAX {
33                    let recovery_id = RecoveryId::from_byte(id).unwrap_or_else(|| unreachable!());
34
35                    if let Some(recovered) = Self::recover(prehash, signature, recovery_id)
36                        && recovered == *public_key
37                    {
38                        return true;
39                    }
40                }
41                // no recovery id was found
42                false
43            }
44            _ => {{
45                use k256::{
46                    ecdsa::signature::hazmat::PrehashVerifier,
47                    elliptic_curve::scalar::IsHigh,
48                };
49
50                if signature.s().is_high().into() {
51                    // guard against signature malleability
52                    return false;
53                }
54
55                public_key.verify_prehash(prehash, signature).is_ok()
56            }}
57        }
58    }
59}
60
61impl RecoverableCurve for Secp256k1 {
62    type RecoveryId = RecoveryId;
63
64    #[inline]
65    fn recover(
66        prehash: &[u8],
67        signature: &Self::Signature,
68        recovery_id: Self::RecoveryId,
69    ) -> Option<Self::PublicKey> {
70        // accept only 32 byte prehash
71        let prehash: &[u8; 32] = prehash.try_into().ok()?;
72
73        let public_key = {
74            cfg_select! {
75                near => {
76                    let pk: [u8; 64] = ::near_sdk::env::ecrecover(
77                        prehash,
78                        &signature.to_bytes(),
79                        recovery_id.to_byte(),
80                        // Do not accept malleable signatures:
81                        // https://github.com/near/nearcore/blob/d73041cc1d1a70af4456fceefaceb1bf7f684fde/core/crypto/src/signature.rs#L448-L455
82                        true,
83                    )?;
84
85                    Secp256k1UncompressedPublicKey(pk).try_into().ok()
86                }
87                _ => {
88                    use k256::elliptic_curve::scalar::IsHigh;
89
90                    if signature.s().is_high().into() {
91                        // guard against signature malleability
92                        return None;
93                    }
94
95                    VerifyingKey::recover_from_prehash(prehash, signature, recovery_id).ok()
96                }
97            }
98        }?;
99
100        Some(public_key)
101    }
102}
103
104#[cfg(feature = "signing")]
105const _: () = {
106    use futures_util::{TryFutureExt, future};
107    use k256::ecdsa::{Error, SigningKey};
108
109    use crate::{RecoverableSigner, Signer};
110
111    impl Signer<Secp256k1> for SigningKey {
112        type Error = Error;
113
114        #[inline]
115        fn public_key(&self) -> <Secp256k1 as Curve>::PublicKey {
116            *self.verifying_key()
117        }
118
119        /// Sign **32-byte prehash** (i.e. output of cryptographic hash
120        /// function).
121        ///
122        /// If given prehash is of different length, an error will be returned.
123        #[inline]
124        fn sign(
125            &self,
126            prehash: &[u8],
127        ) -> impl Future<Output = Result<<Secp256k1 as Curve>::Signature, Self::Error>> + Send
128        {
129            RecoverableSigner::sign_recoverable(self, prehash).map_ok(|s| s.0)
130        }
131    }
132
133    impl RecoverableSigner<Secp256k1> for SigningKey {
134        /// Sign **32-byte prehash** (i.e. output of cryptographic hash
135        /// function) and return a signature along with recovery id.
136        ///
137        /// If given prehash is of different length, an error will be returned.
138        #[inline]
139        fn sign_recoverable(
140            &self,
141            prehash: &[u8],
142        ) -> impl Future<
143            Output = Result<
144                (
145                    <Secp256k1 as Curve>::Signature,
146                    <Secp256k1 as RecoverableCurve>::RecoveryId,
147                ),
148                Self::Error,
149            >,
150        > + Send {
151            let Ok(prehash) = <&[u8; 32]>::try_from(prehash) else {
152                return future::err(Error::from_source("prehash must be 32 bytes long"));
153            };
154
155            // Signature is automatically normalized to be low-S form, since
156            // `<k256::Secp256k1 as EcdsaCurve>::NORMALIZE_S` is set.
157            future::ok(self.sign_prehash_recoverable(prehash))
158        }
159    }
160};
161
162/// Uncompressed Secp256k1 public key **without** leading SEC-1 tag byte.
163#[cfg_attr(
164    feature = "serde",
165    derive(::serde_with::SerializeDisplay, ::serde_with::DeserializeFromStr),
166    cfg_attr(
167        feature = "schemars-v0_8",
168        derive(::schemars::JsonSchema),
169        schemars(example = "Self::example"),
170    )
171)]
172#[cfg_attr(feature = "arbitrary", derive(::arbitrary::Arbitrary))]
173#[cfg_attr(
174    feature = "borsh",
175    derive(::borsh::BorshSerialize, ::borsh::BorshDeserialize),
176    cfg_attr(feature = "borsh-schema", derive(::borsh::BorshSchema))
177)]
178#[derive(
179    Debug,
180    Clone,
181    Copy,
182    PartialEq,
183    Eq,
184    PartialOrd,
185    Ord,
186    Hash,
187    derive_more::AsRef,
188    derive_more::From,
189    derive_more::Into,
190)]
191#[as_ref([u8], [u8; 64])]
192#[into(owned, ref)]
193#[repr(transparent)]
194pub struct Secp256k1UncompressedPublicKey(
195    // schemars@0.8 ignores `with` at struct level for newtypes; must be on the field
196    #[cfg_attr(feature = "schemars-v0_8", schemars(with = "String"))] pub [u8; 64],
197);
198
199impl Secp256k1UncompressedPublicKey {
200    /// Compress public key
201    ///
202    /// # Examples
203    ///
204    /// ```rust
205    /// # use defuse_crypto::secp256k1::{
206    /// #     Secp256k1CompressedPublicKey,
207    /// #     Secp256k1UncompressedPublicKey,
208    /// # };
209    /// # use hex_literal::hex;
210    /// assert_eq!(
211    ///     Secp256k1UncompressedPublicKey(hex!("85a66984273f338ce4ef7b85e5430b008307e8591bb7c1b980852cf6423770b801f41e9438155eb53a5e20f748640093bb42ae3aeca035f7b7fd7a1a21f22f68"))
212    ///         .compress()
213    ///         .unwrap()
214    ///         .0,
215    ///     hex!("0285a66984273f338ce4ef7b85e5430b008307e8591bb7c1b980852cf6423770b8"),
216    /// );
217    /// ```
218    #[inline]
219    pub fn compress(&self) -> Result<Secp256k1CompressedPublicKey, k256::ecdsa::Error> {
220        VerifyingKey::try_from(self).map(Into::into)
221    }
222
223    #[cfg(feature = "schemars-v0_8")]
224    const fn example() -> Self {
225        Self(hex_literal::hex!(
226            "85a66984273f338ce4ef7b85e5430b008307e8591bb7c1b980852cf6423770b801f41e9438155eb53a5e20f748640093bb42ae3aeca035f7b7fd7a1a21f22f68"
227        ))
228    }
229}
230
231impl From<VerifyingKey> for Secp256k1UncompressedPublicKey {
232    #[inline]
233    fn from(value: VerifyingKey) -> Self {
234        (&value).into()
235    }
236}
237
238impl From<&VerifyingKey> for Secp256k1UncompressedPublicKey {
239    #[inline]
240    fn from(value: &VerifyingKey) -> Self {
241        Self(
242            value
243                .to_sec1_point(false) // do not compress
244                .as_bytes()[1..] // skip SEC-1 leading tag byte
245                .try_into()
246                .unwrap_or_else(|_| unreachable!()),
247        )
248    }
249}
250
251impl TryFrom<Secp256k1UncompressedPublicKey> for VerifyingKey {
252    type Error = k256::ecdsa::Error;
253
254    #[inline]
255    fn try_from(value: Secp256k1UncompressedPublicKey) -> Result<Self, Self::Error> {
256        (&value).try_into()
257    }
258}
259
260impl TryFrom<&Secp256k1UncompressedPublicKey> for VerifyingKey {
261    type Error = k256::ecdsa::Error;
262
263    #[inline]
264    fn try_from(value: &Secp256k1UncompressedPublicKey) -> Result<Self, Self::Error> {
265        Self::from_sec1_point(&Sec1Point::from_untagged_bytes((&value.0).into()))
266    }
267}
268
269impl TryFrom<Secp256k1UncompressedPublicKey> for Secp256k1CompressedPublicKey {
270    type Error = k256::ecdsa::Error;
271
272    #[inline]
273    fn try_from(pk: Secp256k1UncompressedPublicKey) -> Result<Self, Self::Error> {
274        (&pk).try_into()
275    }
276}
277
278impl TryFrom<&Secp256k1UncompressedPublicKey> for Secp256k1CompressedPublicKey {
279    type Error = k256::ecdsa::Error;
280
281    #[inline]
282    fn try_from(pk: &Secp256k1UncompressedPublicKey) -> Result<Self, Self::Error> {
283        pk.compress()
284    }
285}
286
287/// Compressed Secp256k1 public key **with** leading SEC-1 tag byte.
288#[cfg_attr(
289    feature = "serde",
290    derive(::serde_with::SerializeDisplay, ::serde_with::DeserializeFromStr),
291    cfg_attr(
292        feature = "schemars-v0_8",
293        derive(::schemars::JsonSchema),
294        schemars(example = "Self::example"),
295    )
296)]
297#[cfg_attr(feature = "arbitrary", derive(::arbitrary::Arbitrary))]
298#[cfg_attr(
299    feature = "borsh",
300    derive(::borsh::BorshSerialize, ::borsh::BorshDeserialize),
301    cfg_attr(feature = "borsh-schema", derive(::borsh::BorshSchema))
302)]
303#[derive(
304    Debug,
305    Clone,
306    Copy,
307    PartialEq,
308    Eq,
309    PartialOrd,
310    Ord,
311    Hash,
312    derive_more::AsRef,
313    derive_more::From,
314    derive_more::Into,
315)]
316#[as_ref([u8], [u8; 33])]
317#[into(owned, ref)]
318#[repr(transparent)]
319pub struct Secp256k1CompressedPublicKey(
320    // schemars@0.8 ignores `with` at struct level for newtypes; must be on the field
321    #[cfg_attr(feature = "schemars-v0_8", schemars(with = "String"))] pub [u8; 33],
322);
323
324impl Secp256k1CompressedPublicKey {
325    #[cfg(feature = "schemars-v0_8")]
326    const fn example() -> Self {
327        Self(hex_literal::hex!(
328            "0285a66984273f338ce4ef7b85e5430b008307e8591bb7c1b980852cf6423770b8"
329        ))
330    }
331}
332
333impl From<VerifyingKey> for Secp256k1CompressedPublicKey {
334    #[inline]
335    fn from(value: VerifyingKey) -> Self {
336        (&value).into()
337    }
338}
339
340impl From<&VerifyingKey> for Secp256k1CompressedPublicKey {
341    #[inline]
342    fn from(value: &VerifyingKey) -> Self {
343        Self(
344            value
345                .to_sec1_point(true) // compress
346                .as_bytes()
347                .try_into()
348                // compressed key is exactly 33 bytes
349                .unwrap_or_else(|_| unreachable!()),
350        )
351    }
352}
353
354impl TryFrom<Secp256k1CompressedPublicKey> for VerifyingKey {
355    type Error = k256::ecdsa::Error;
356
357    #[inline]
358    fn try_from(value: Secp256k1CompressedPublicKey) -> Result<Self, Self::Error> {
359        (&value).try_into()
360    }
361}
362
363impl TryFrom<&Secp256k1CompressedPublicKey> for VerifyingKey {
364    type Error = k256::ecdsa::Error;
365
366    #[inline]
367    fn try_from(value: &Secp256k1CompressedPublicKey) -> Result<Self, Self::Error> {
368        Self::from_sec1_bytes(&value.0)
369    }
370}
371
372impl TryFrom<Secp256k1CompressedPublicKey> for Secp256k1UncompressedPublicKey {
373    type Error = k256::ecdsa::Error;
374
375    #[inline]
376    fn try_from(pk: Secp256k1CompressedPublicKey) -> Result<Self, Self::Error> {
377        (&pk).try_into()
378    }
379}
380
381impl TryFrom<&Secp256k1CompressedPublicKey> for Secp256k1UncompressedPublicKey {
382    type Error = k256::ecdsa::Error;
383
384    #[inline]
385    fn try_from(pk: &Secp256k1CompressedPublicKey) -> Result<Self, Self::Error> {
386        VerifyingKey::try_from(pk).map(Into::into)
387    }
388}
389
390/// Secp256k1 signature **without** recovery byte
391#[cfg_attr(
392    feature = "serde",
393    derive(::serde_with::SerializeDisplay, ::serde_with::DeserializeFromStr),
394    cfg_attr(
395        feature = "schemars-v0_8",
396        derive(::schemars::JsonSchema),
397        schemars(example = "Self::example"),
398    )
399)]
400#[cfg_attr(feature = "arbitrary", derive(::arbitrary::Arbitrary))]
401#[cfg_attr(
402    feature = "borsh",
403    derive(::borsh::BorshSerialize, ::borsh::BorshDeserialize),
404    cfg_attr(feature = "borsh-schema", derive(::borsh::BorshSchema))
405)]
406#[derive(
407    Debug,
408    Clone,
409    Copy,
410    PartialEq,
411    Eq,
412    PartialOrd,
413    Ord,
414    Hash,
415    derive_more::AsRef,
416    derive_more::From,
417    derive_more::Into,
418)]
419#[as_ref([u8], [u8; 64])]
420#[into(owned, ref)]
421#[repr(transparent)]
422pub struct Secp256k1Signature(
423    // schemars@0.8 ignores `with` at struct level for newtypes; must be on the field
424    #[cfg_attr(feature = "schemars-v0_8", schemars(with = "String"))] pub [u8; 64],
425);
426
427impl Secp256k1Signature {
428    #[cfg(feature = "schemars-v0_8")]
429    const fn example() -> Self {
430        Self(hex_literal::hex!(
431            "7800a70d05cde2c49ed546a6ce887ce6027c2c268c0285f6efef0cdfc4366b23643790f67a86468ee8301ed12cfffcb07c6530f90a9327ec057800fabd332e47"
432        ))
433    }
434}
435
436impl From<Signature> for Secp256k1Signature {
437    #[inline]
438    fn from(value: Signature) -> Self {
439        (&value).into()
440    }
441}
442
443impl From<&Signature> for Secp256k1Signature {
444    #[inline]
445    fn from(value: &Signature) -> Self {
446        Self(value.to_bytes().into())
447    }
448}
449
450impl TryFrom<Secp256k1Signature> for Signature {
451    type Error = k256::ecdsa::Error;
452
453    #[inline]
454    fn try_from(value: Secp256k1Signature) -> Result<Self, Self::Error> {
455        (&value).try_into()
456    }
457}
458
459impl TryFrom<&Secp256k1Signature> for Signature {
460    type Error = k256::ecdsa::Error;
461
462    #[inline]
463    fn try_from(value: &Secp256k1Signature) -> Result<Self, Self::Error> {
464        Self::from_bytes((&value.0).into())
465    }
466}
467
468/// Recoverable 65-byte secp256k1 signature, i.e. 64-byte signature with
469/// additional recovery byte
470#[cfg_attr(
471    feature = "serde",
472    derive(::serde_with::SerializeDisplay, ::serde_with::DeserializeFromStr),
473    cfg_attr(
474        feature = "schemars-v0_8",
475        derive(::schemars::JsonSchema),
476        schemars(example = "Self::example"),
477    )
478)]
479#[cfg_attr(feature = "arbitrary", derive(::arbitrary::Arbitrary))]
480#[cfg_attr(
481    feature = "borsh",
482    derive(::borsh::BorshSerialize, ::borsh::BorshDeserialize),
483    cfg_attr(feature = "borsh-schema", derive(::borsh::BorshSchema))
484)]
485#[derive(
486    Debug,
487    Clone,
488    Copy,
489    PartialEq,
490    Eq,
491    PartialOrd,
492    Ord,
493    Hash,
494    derive_more::AsRef,
495    derive_more::From,
496    derive_more::Into,
497)]
498#[as_ref([u8], [u8; 65])]
499#[into(owned, ref)]
500#[repr(transparent)]
501pub struct Secp256k1RecoverableSignature(
502    // schemars@0.8 ignores `with` at struct level for newtypes; must be on the field
503    #[cfg_attr(feature = "schemars-v0_8", schemars(with = "String"))] pub [u8; 65],
504);
505
506impl Secp256k1RecoverableSignature {
507    #[cfg(feature = "schemars-v0_8")]
508    const fn example() -> Self {
509        Self(hex_literal::hex!(
510            "7800a70d05cde2c49ed546a6ce887ce6027c2c268c0285f6efef0cdfc4366b23643790f67a86468ee8301ed12cfffcb07c6530f90a9327ec057800fabd332e4701"
511        ))
512    }
513}
514
515impl From<Secp256k1RecoverableSignature> for Secp256k1Signature {
516    #[inline]
517    fn from(sig: Secp256k1RecoverableSignature) -> Self {
518        let [sig @ .., _v] = sig.0;
519        Self(sig)
520    }
521}
522
523impl From<(Signature, RecoveryId)> for Secp256k1RecoverableSignature {
524    #[inline]
525    fn from((signature, recovery_id): (Signature, RecoveryId)) -> Self {
526        (&signature, recovery_id).into()
527    }
528}
529
530impl From<(&Signature, RecoveryId)> for Secp256k1RecoverableSignature {
531    #[inline]
532    fn from((signature, recovery_id): (&Signature, RecoveryId)) -> Self {
533        let mut buf = [0u8; 65];
534        buf[..64].copy_from_slice(&signature.to_bytes());
535        buf[64] = recovery_id.to_byte();
536        Self(buf)
537    }
538}
539
540impl TryFrom<Secp256k1RecoverableSignature> for (Signature, RecoveryId) {
541    type Error = k256::ecdsa::Error;
542
543    #[inline]
544    fn try_from(value: Secp256k1RecoverableSignature) -> Result<Self, Self::Error> {
545        (&value).try_into()
546    }
547}
548
549impl TryFrom<Secp256k1RecoverableSignature> for Signature {
550    type Error = k256::ecdsa::Error;
551
552    #[inline]
553    fn try_from(value: Secp256k1RecoverableSignature) -> Result<Self, Self::Error> {
554        (&value).try_into()
555    }
556}
557
558impl TryFrom<&Secp256k1RecoverableSignature> for (Signature, RecoveryId) {
559    type Error = k256::ecdsa::Error;
560
561    #[inline]
562    fn try_from(value: &Secp256k1RecoverableSignature) -> Result<Self, Self::Error> {
563        let [ref signature @ .., recovery_id] = value.0;
564        Ok((
565            Signature::from_bytes(signature.into())?,
566            RecoveryId::from_byte(recovery_id).ok_or_else(k256::ecdsa::Error::new)?,
567        ))
568    }
569}
570
571impl TryFrom<&Secp256k1RecoverableSignature> for Signature {
572    type Error = k256::ecdsa::Error;
573
574    #[inline]
575    fn try_from(value: &Secp256k1RecoverableSignature) -> Result<Self, Self::Error> {
576        <(Self, RecoveryId)>::try_from(value).map(|t| t.0)
577    }
578}
579
580#[cfg(feature = "fmt")]
581const _: () = {
582    use core::{
583        fmt::{self, Display},
584        str::FromStr,
585    };
586
587    use crate::fmt::{ParseCurveError, TypedCurve};
588
589    impl TypedCurve for Secp256k1 {
590        const CURVE_TYPE: &str = "secp256k1";
591    }
592
593    impl Display for Secp256k1CompressedPublicKey {
594        #[inline]
595        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
596            f.write_str(&Secp256k1::to_base58(self.0))
597        }
598    }
599
600    impl FromStr for Secp256k1CompressedPublicKey {
601        type Err = ParseCurveError;
602
603        #[inline]
604        fn from_str(s: &str) -> Result<Self, Self::Err> {
605            Secp256k1::parse_base58(s).map(Self)
606        }
607    }
608
609    impl Display for Secp256k1UncompressedPublicKey {
610        #[inline]
611        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
612            f.write_str(&Secp256k1::to_base58(self.0))
613        }
614    }
615
616    impl FromStr for Secp256k1UncompressedPublicKey {
617        type Err = ParseCurveError;
618
619        #[inline]
620        fn from_str(s: &str) -> Result<Self, Self::Err> {
621            Secp256k1::parse_base58(s).map(Self)
622        }
623    }
624
625    impl Display for Secp256k1Signature {
626        #[inline]
627        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
628            f.write_str(&Secp256k1::to_base58(self.0))
629        }
630    }
631
632    impl FromStr for Secp256k1Signature {
633        type Err = ParseCurveError;
634
635        #[inline]
636        fn from_str(s: &str) -> Result<Self, Self::Err> {
637            Secp256k1::parse_base58(s).map(Self)
638        }
639    }
640
641    impl Display for Secp256k1RecoverableSignature {
642        #[inline]
643        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
644            f.write_str(&Secp256k1::to_base58(self.0))
645        }
646    }
647
648    impl FromStr for Secp256k1RecoverableSignature {
649        type Err = ParseCurveError;
650
651        #[inline]
652        fn from_str(s: &str) -> Result<Self, Self::Err> {
653            Secp256k1::parse_base58(s).map(Self)
654        }
655    }
656};
657
658#[cfg(test)]
659mod tests {
660    use hex_literal::hex;
661    use k256::{ecdsa::SigningKey, elliptic_curve::Generate};
662    use rstest::rstest;
663
664    use crate::tests::test_sign_recover;
665
666    use super::*;
667
668    #[rstest]
669    #[case(
670        hex!("85a66984273f338ce4ef7b85e5430b008307e8591bb7c1b980852cf6423770b801f41e9438155eb53a5e20f748640093bb42ae3aeca035f7b7fd7a1a21f22f68"),
671        hex!("aa05af77f274774b8bdc7b61d98bc40da523dc2821fdea555f4d6aa413199bcc"),
672        hex!("7800a70d05cde2c49ed546a6ce887ce6027c2c268c0285f6efef0cdfc4366b23643790f67a86468ee8301ed12cfffcb07c6530f90a9327ec057800fabd332e4701"),
673    )]
674    #[case(
675        hex!("85a66984273f338ce4ef7b85e5430b008307e8591bb7c1b980852cf6423770b801f41e9438155eb53a5e20f748640093bb42ae3aeca035f7b7fd7a1a21f22f68"),
676        hex!("1632c0ebba467e157675403ba3ba280b836e1801b5678d878dfc90bfc403d6e1"),
677        hex!("eea1651a60600ec4d9c45e8ae81da1a78377f789f0ac2019de66ad943459913015ef9256809ee0e6bb76e303a0b4802e475c1d26ade5d585292b80c9fe9cb10c01"),
678    )]
679    fn verify_ok(
680        #[case] public_key: impl Into<Secp256k1UncompressedPublicKey>,
681        #[case] prehash: [u8; 32],
682        #[case] signature: impl Into<Secp256k1RecoverableSignature>,
683    ) {
684        assert!(
685            Secp256k1::verify(
686                &public_key.into().try_into().unwrap(),
687                &prehash,
688                &signature.into().try_into().unwrap(),
689            ),
690            "signature is invalid",
691        );
692    }
693
694    #[rstest]
695    #[case(
696        hex!("85a66984273f338ce4ef7b85e5430b008307e8591bb7c1b980852cf6423770b801f41e9438155eb53a5e20f748640093bb42ae3aeca035f7b7fd7a1a21f22f68"),
697        hex!("1632c0ebba467e157675403ba3ba280b836e1801b5678d878dfc90bfc403d6e1"),
698        hex!("7800a70d05cde2c49ed546a6ce887ce6027c2c268c0285f6efef0cdfc4366b23643790f67a86468ee8301ed12cfffcb07c6530f90a9327ec057800fabd332e4701"),
699    )]
700    #[case(
701        hex!("85a66984273f338ce4ef7b85e5430b008307e8591bb7c1b980852cf6423770b801f41e9438155eb53a5e20f748640093bb42ae3aeca035f7b7fd7a1a21f22f68"),
702        hex!("aa05af77f274774b8bdc7b61d98bc40da523dc2821fdea555f4d6aa413199bcc"),
703        hex!("eea1651a60600ec4d9c45e8ae81da1a78377f789f0ac2019de66ad943459913015ef9256809ee0e6bb76e303a0b4802e475c1d26ade5d585292b80c9fe9cb10c01"),
704    )]
705    fn verify_fail(
706        #[case] public_key: impl Into<Secp256k1UncompressedPublicKey>,
707        #[case] prehash: [u8; 32],
708        #[case] signature: impl Into<Secp256k1RecoverableSignature>,
709    ) {
710        assert!(
711            !Secp256k1::verify(
712                &public_key.into().try_into().unwrap(),
713                &prehash,
714                &signature.into().try_into().unwrap(),
715            ),
716            "invalid signature passed verification",
717        );
718    }
719
720    #[rstest]
721    #[case(
722        hex!("85a66984273f338ce4ef7b85e5430b008307e8591bb7c1b980852cf6423770b801f41e9438155eb53a5e20f748640093bb42ae3aeca035f7b7fd7a1a21f22f68"),
723        hex!("aa05af77f274774b8bdc7b61d98bc40da523dc2821fdea555f4d6aa413199bcc"),
724        hex!("7800a70d05cde2c49ed546a6ce887ce6027c2c268c0285f6efef0cdfc4366b23643790f67a86468ee8301ed12cfffcb07c6530f90a9327ec057800fabd332e4701"),
725    )]
726    #[case(
727        hex!("85a66984273f338ce4ef7b85e5430b008307e8591bb7c1b980852cf6423770b801f41e9438155eb53a5e20f748640093bb42ae3aeca035f7b7fd7a1a21f22f68"),
728        hex!("1632c0ebba467e157675403ba3ba280b836e1801b5678d878dfc90bfc403d6e1"),
729        hex!("eea1651a60600ec4d9c45e8ae81da1a78377f789f0ac2019de66ad943459913015ef9256809ee0e6bb76e303a0b4802e475c1d26ade5d585292b80c9fe9cb10c01"),
730    )]
731    fn recover_ok(
732        #[case] public_key: impl Into<Secp256k1UncompressedPublicKey>,
733        #[case] prehash: [u8; 32],
734        #[case] signature: impl Into<Secp256k1RecoverableSignature>,
735    ) {
736        let (signature, recovery_id) = signature.into().try_into().unwrap();
737
738        assert_eq!(
739            Secp256k1::recover(&prehash, &signature, recovery_id),
740            Some(public_key.into().try_into().unwrap()),
741            "invalid recovered public key",
742        );
743    }
744
745    #[rstest]
746    #[case(
747        hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"),
748    )]
749    #[case(
750        hex!("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"),
751    )]
752    #[tokio::test]
753    async fn sign_recover(#[case] prehash: [u8; 32]) {
754        test_sign_recover(SigningKey::generate(), prehash).await;
755    }
756}