Skip to main content

defuse_crypto/
p256.rs

1//! P256 (a.k.a. secp256r1) Elliptic Curve Digital Signature Algorithm
2
3pub use p256;
4use p256::{
5    Sec1Point,
6    ecdsa::{Signature, VerifyingKey},
7    elliptic_curve::scalar::IsHigh,
8};
9
10use crate::Curve;
11
12/// P256 (a.k.a. secp256r1) Elliptic Curve Digital Signature Algorithm
13pub struct P256;
14
15impl Curve for P256 {
16    type PublicKey = VerifyingKey;
17
18    type Signature = Signature;
19
20    /// Verify P256 signature over **32-byte prehash** (i.e. output of
21    /// cryptographic hash function) for given public key.
22    #[inline]
23    fn verify(public_key: &Self::PublicKey, prehash: &[u8], signature: &Self::Signature) -> bool {
24        // accept only 32 byte prehash
25        let Ok(prehash) = <&[u8; 32]>::try_from(prehash) else {
26            return false;
27        };
28
29        if signature.s().is_high().into() {
30            // guard against signature malleability
31            return false;
32        }
33
34        cfg_select! {
35            near => {
36                ::near_sdk::env::p256_verify(
37                    signature.to_bytes().as_ref(),
38                    prehash,
39                    P256CompressedPublicKey::from(public_key).as_ref(),
40                )
41            }
42            _ => {{
43                use p256::ecdsa::signature::hazmat::PrehashVerifier;
44
45                public_key.verify_prehash(prehash, signature).is_ok()
46            }}
47        }
48    }
49}
50
51#[cfg(feature = "signing")]
52const _: () = {
53    use futures_util::future;
54    use p256::ecdsa::{self, SigningKey};
55
56    use crate::Signer;
57
58    impl Signer<P256> for SigningKey {
59        type Error = ecdsa::Error;
60
61        #[inline]
62        fn public_key(&self) -> <P256 as Curve>::PublicKey {
63            *self.verifying_key()
64        }
65
66        /// Sign **32-byte prehash** (i.e. output of cryptographic hash
67        /// function).
68        ///
69        /// If given prehash is of different length, an error will be returned.
70        #[inline]
71        fn sign(
72            &self,
73            prehash: &[u8],
74        ) -> impl Future<Output = Result<<P256 as Curve>::Signature, Self::Error>> + Send {
75            let Ok(prehash) = <&[u8; 32]>::try_from(prehash) else {
76                return future::err(ecdsa::Error::from_source("prehash must be 32 bytes long"));
77            };
78
79            let signature = self.sign_prehash_recoverable(prehash).0;
80            // Signature is **not** automatically normalized to be low-S
81            // form, since `<p256::NistP256 as EcdsaCurve>::NORMALIZE_S`
82            // is not set. So, we need to normalize ourselves:
83            future::ok(signature.normalize_s())
84        }
85    }
86};
87
88/// Uncompressed P256 public key **without** leading SEC-1 tag byte.
89#[cfg_attr(
90    feature = "serde",
91    derive(::serde_with::SerializeDisplay, ::serde_with::DeserializeFromStr),
92    cfg_attr(
93        feature = "schemars-v0_8",
94        derive(::schemars::JsonSchema),
95        schemars(example = "Self::example"),
96    )
97)]
98#[cfg_attr(feature = "arbitrary", derive(::arbitrary::Arbitrary))]
99#[cfg_attr(
100    feature = "borsh",
101    derive(::borsh::BorshSerialize, ::borsh::BorshDeserialize),
102    cfg_attr(feature = "borsh-schema", derive(::borsh::BorshSchema))
103)]
104#[derive(
105    Debug,
106    Clone,
107    Copy,
108    PartialEq,
109    Eq,
110    PartialOrd,
111    Ord,
112    Hash,
113    derive_more::AsRef,
114    derive_more::From,
115    derive_more::Into,
116)]
117#[as_ref([u8], [u8; 64])]
118#[into(owned, ref)]
119#[repr(transparent)]
120pub struct P256UncompressedPublicKey(
121    // schemars@0.8 ignores `with` at struct level for newtypes; must be on the field
122    #[cfg_attr(feature = "schemars-v0_8", schemars(with = "String"))] pub [u8; 64],
123);
124
125impl P256UncompressedPublicKey {
126    /// Compress public key
127    ///
128    /// # Examples
129    ///
130    /// ```rust
131    /// # use defuse_crypto::p256::{
132    /// #     P256CompressedPublicKey,
133    /// #     P256UncompressedPublicKey,
134    /// # };
135    /// # use hex_literal::hex;
136    /// assert_eq!(
137    ///     P256UncompressedPublicKey(hex!("beed8cb2c3622dd5f1ee641f12d88e35f3fb8c6ae081d689008bdaa6af38d4408e9c469c5ca7b59927606ef9ea34ee2335e85dbeaa265ca038b5e2896f34ded0"))
138    ///         .compress()
139    ///         .unwrap()
140    ///         .0,
141    ///     hex!("02beed8cb2c3622dd5f1ee641f12d88e35f3fb8c6ae081d689008bdaa6af38d440"),
142    /// );
143    /// ```
144    #[inline]
145    pub fn compress(&self) -> Result<P256CompressedPublicKey, p256::ecdsa::Error> {
146        VerifyingKey::try_from(self).map(Into::into)
147    }
148
149    #[cfg(feature = "schemars-v0_8")]
150    const fn example() -> Self {
151        Self(hex_literal::hex!(
152            "b87da10683d04e6ec4e2f1775556a63cbb01be843058eb737fe02f9e22663093cf61b416dfb33ba2e32e9c6f3a09d84fa90d0ab9709f4a298d52e0799c8217f5"
153        ))
154    }
155}
156
157impl From<VerifyingKey> for P256UncompressedPublicKey {
158    #[inline]
159    fn from(value: VerifyingKey) -> Self {
160        (&value).into()
161    }
162}
163
164impl From<&VerifyingKey> for P256UncompressedPublicKey {
165    #[inline]
166    fn from(value: &VerifyingKey) -> Self {
167        Self(
168            value
169                .to_sec1_point(false) // do not compress
170                .as_bytes()[1..] // skip SEC-1 leading tag byte
171                .try_into()
172                .unwrap_or_else(|_| unreachable!()),
173        )
174    }
175}
176
177impl TryFrom<P256UncompressedPublicKey> for VerifyingKey {
178    type Error = p256::ecdsa::Error;
179
180    #[inline]
181    fn try_from(value: P256UncompressedPublicKey) -> Result<Self, Self::Error> {
182        (&value).try_into()
183    }
184}
185
186impl TryFrom<&P256UncompressedPublicKey> for VerifyingKey {
187    type Error = p256::ecdsa::Error;
188
189    #[inline]
190    fn try_from(value: &P256UncompressedPublicKey) -> Result<Self, Self::Error> {
191        Self::from_sec1_point(&Sec1Point::from_untagged_bytes((&value.0).into()))
192    }
193}
194
195/// Compressed P256 public key, i.e. `x` coordinate **with** leading SEC-1 tag byte.
196#[cfg_attr(
197    feature = "serde",
198    derive(::serde_with::SerializeDisplay, ::serde_with::DeserializeFromStr),
199    cfg_attr(
200        feature = "schemars-v0_8",
201        derive(::schemars::JsonSchema),
202        schemars(example = "Self::example"),
203    )
204)]
205#[cfg_attr(feature = "arbitrary", derive(::arbitrary::Arbitrary))]
206#[cfg_attr(
207    feature = "borsh",
208    derive(::borsh::BorshSerialize, ::borsh::BorshDeserialize),
209    cfg_attr(feature = "borsh-schema", derive(::borsh::BorshSchema))
210)]
211#[derive(
212    Debug,
213    Clone,
214    Copy,
215    PartialEq,
216    Eq,
217    PartialOrd,
218    Ord,
219    Hash,
220    derive_more::AsRef,
221    derive_more::From,
222    derive_more::Into,
223)]
224#[as_ref([u8], [u8; 33])]
225#[into(owned, ref)]
226#[repr(transparent)]
227pub struct P256CompressedPublicKey(
228    // schemars@0.8 ignores `with` at struct level for newtypes; must be on the field
229    #[cfg_attr(feature = "schemars-v0_8", schemars(with = "String"))] pub [u8; 33],
230);
231
232impl P256CompressedPublicKey {
233    #[cfg(feature = "schemars-v0_8")]
234    const fn example() -> Self {
235        Self(hex_literal::hex!(
236            "03b87da10683d04e6ec4e2f1775556a63cbb01be843058eb737fe02f9e22663093"
237        ))
238    }
239}
240
241impl From<VerifyingKey> for P256CompressedPublicKey {
242    #[inline]
243    fn from(value: VerifyingKey) -> Self {
244        (&value).into()
245    }
246}
247
248impl From<&VerifyingKey> for P256CompressedPublicKey {
249    #[inline]
250    fn from(value: &VerifyingKey) -> Self {
251        Self(
252            value
253                .to_sec1_point(true) // compress
254                .as_bytes()
255                .try_into()
256                // compressed key is exactly 33 bytes
257                .unwrap_or_else(|_| unreachable!()),
258        )
259    }
260}
261
262impl TryFrom<P256CompressedPublicKey> for VerifyingKey {
263    type Error = p256::ecdsa::Error;
264
265    #[inline]
266    fn try_from(value: P256CompressedPublicKey) -> Result<Self, Self::Error> {
267        (&value).try_into()
268    }
269}
270
271impl TryFrom<&P256CompressedPublicKey> for VerifyingKey {
272    type Error = p256::ecdsa::Error;
273
274    #[inline]
275    fn try_from(value: &P256CompressedPublicKey) -> Result<Self, Self::Error> {
276        Self::from_sec1_bytes(&value.0)
277    }
278}
279
280impl TryFrom<P256UncompressedPublicKey> for P256CompressedPublicKey {
281    type Error = p256::ecdsa::Error;
282
283    /// Compress a public key
284    #[inline]
285    fn try_from(value: P256UncompressedPublicKey) -> Result<Self, Self::Error> {
286        (&value).try_into()
287    }
288}
289
290impl TryFrom<&P256UncompressedPublicKey> for P256CompressedPublicKey {
291    type Error = p256::ecdsa::Error;
292
293    /// Compress a public key
294    #[inline]
295    fn try_from(value: &P256UncompressedPublicKey) -> Result<Self, Self::Error> {
296        value.compress()
297    }
298}
299
300/// P256 signature
301#[cfg_attr(
302    feature = "serde",
303    derive(::serde_with::SerializeDisplay, ::serde_with::DeserializeFromStr),
304    cfg_attr(
305        feature = "schemars-v0_8",
306        derive(::schemars::JsonSchema),
307        schemars(example = "Self::example"),
308    )
309)]
310#[cfg_attr(feature = "arbitrary", derive(::arbitrary::Arbitrary))]
311#[cfg_attr(
312    feature = "borsh",
313    derive(::borsh::BorshSerialize, ::borsh::BorshDeserialize),
314    cfg_attr(feature = "borsh-schema", derive(::borsh::BorshSchema))
315)]
316#[derive(
317    Debug,
318    Clone,
319    Copy,
320    PartialEq,
321    Eq,
322    PartialOrd,
323    Ord,
324    Hash,
325    derive_more::AsRef,
326    derive_more::From,
327    derive_more::Into,
328)]
329#[as_ref([u8], [u8; 64])]
330#[into(owned, ref)]
331#[repr(transparent)]
332pub struct P256Signature(
333    // schemars@0.8 ignores `with` at struct level for newtypes; must be on the field
334    #[cfg_attr(feature = "schemars-v0_8", schemars(with = "String"))] pub [u8; 64],
335);
336
337impl P256Signature {
338    #[cfg(feature = "schemars-v0_8")]
339    const fn example() -> Self {
340        Self(hex_literal::hex!(
341            "002527c17a17d709ab62ffab033e0a26d901f5bee6686a0d605032828e490a7c47a9f4161e6a17688ae42a66adb67c9c22c86153afb08103b1e9eccd5314c271"
342        ))
343    }
344}
345
346impl From<Signature> for P256Signature {
347    #[inline]
348    fn from(value: Signature) -> Self {
349        (&value).into()
350    }
351}
352
353impl From<&Signature> for P256Signature {
354    #[inline]
355    fn from(value: &Signature) -> Self {
356        Self(value.to_bytes().into())
357    }
358}
359
360impl TryFrom<P256Signature> for Signature {
361    type Error = p256::ecdsa::Error;
362
363    #[inline]
364    fn try_from(value: P256Signature) -> Result<Self, Self::Error> {
365        (&value).try_into()
366    }
367}
368
369impl TryFrom<&P256Signature> for Signature {
370    type Error = p256::ecdsa::Error;
371
372    #[inline]
373    fn try_from(value: &P256Signature) -> Result<Self, Self::Error> {
374        Self::from_bytes((&value.0).into())
375    }
376}
377
378#[cfg(feature = "fmt")]
379const _: () = {
380    use core::{
381        fmt::{self, Display},
382        str::FromStr,
383    };
384
385    use crate::fmt::{ParseCurveError, TypedCurve};
386
387    impl TypedCurve for P256 {
388        const CURVE_TYPE: &str = "p256";
389    }
390
391    impl Display for P256UncompressedPublicKey {
392        #[inline]
393        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
394            f.write_str(&P256::to_base58(self.0))
395        }
396    }
397
398    impl FromStr for P256UncompressedPublicKey {
399        type Err = ParseCurveError;
400
401        #[inline]
402        fn from_str(s: &str) -> Result<Self, Self::Err> {
403            P256::parse_base58(s).map(Self)
404        }
405    }
406
407    impl Display for P256CompressedPublicKey {
408        #[inline]
409        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
410            f.write_str(&P256::to_base58(self.0))
411        }
412    }
413
414    impl FromStr for P256CompressedPublicKey {
415        type Err = ParseCurveError;
416
417        #[inline]
418        fn from_str(s: &str) -> Result<Self, Self::Err> {
419            P256::parse_base58(s).map(Self)
420        }
421    }
422
423    impl Display for P256Signature {
424        #[inline]
425        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
426            f.write_str(&P256::to_base58(self.0))
427        }
428    }
429
430    impl FromStr for P256Signature {
431        type Err = ParseCurveError;
432
433        #[inline]
434        fn from_str(s: &str) -> Result<Self, Self::Err> {
435            P256::parse_base58(s).map(Self)
436        }
437    }
438};
439
440#[cfg(test)]
441mod tests {
442    use hex_literal::hex;
443    use p256::{ecdsa::SigningKey, elliptic_curve::Generate};
444    use rstest::rstest;
445
446    use crate::tests::test_sign_verify;
447
448    use super::*;
449
450    #[rstest]
451    #[case(
452        hex!("03b87da10683d04e6ec4e2f1775556a63cbb01be843058eb737fe02f9e22663093"),
453        hex!("0fc6357f688865a5a5ce1ac484f8ade578325ccda012091a533924404890e794"),
454        hex!("002527c17a17d709ab62ffab033e0a26d901f5bee6686a0d605032828e490a7c47a9f4161e6a17688ae42a66adb67c9c22c86153afb08103b1e9eccd5314c271"),
455    )]
456    #[case(
457        hex!("03f616f10f0841ff81b0caa52859ee168d03b657c6adef468761289fe75b8e291c"),
458        hex!("b64fd2202ddf8ef361dcbdf224fe6c2a06bd1fc67f40a89cb49a92d2071ffecc"),
459        hex!("6bb2de380eefd199ee44364902f4b89679b8df04024e24047dc0ec1c61c11b2d527495fed4a6903ebe086e831bd000bf683c2a36f0454eaccaecf109678fd33e"),
460    )]
461    fn verify_ok(
462        #[case] public_key: impl Into<P256CompressedPublicKey>,
463        #[case] prehash: [u8; 32],
464        #[case] signature: impl Into<P256Signature>,
465    ) {
466        assert!(
467            P256::verify(
468                &public_key.into().try_into().unwrap(),
469                &prehash,
470                &signature.into().try_into().unwrap(),
471            ),
472            "signature is invalid",
473        );
474    }
475
476    #[rstest]
477    #[case(
478        hex!("03b87da10683d04e6ec4e2f1775556a63cbb01be843058eb737fe02f9e22663093"),
479        hex!("0fc6357f688865a5a5ce1ac484f8ade578325ccda012091a533924404890e794"),
480        hex!("6bb2de380eefd199ee44364902f4b89679b8df04024e24047dc0ec1c61c11b2d527495fed4a6903ebe086e831bd000bf683c2a36f0454eaccaecf109678fd33e"),
481    )]
482    #[case(
483        hex!("03f616f10f0841ff81b0caa52859ee168d03b657c6adef468761289fe75b8e291c"),
484        hex!("b64fd2202ddf8ef361dcbdf224fe6c2a06bd1fc67f40a89cb49a92d2071ffecc"),
485        hex!("002527c17a17d709ab62ffab033e0a26d901f5bee6686a0d605032828e490a7c47a9f4161e6a17688ae42a66adb67c9c22c86153afb08103b1e9eccd5314c271"),
486    )]
487    #[case(
488        hex!("03b87da10683d04e6ec4e2f1775556a63cbb01be843058eb737fe02f9e22663093"),
489        hex!("b64fd2202ddf8ef361dcbdf224fe6c2a06bd1fc67f40a89cb49a92d2071ffecc"),
490        hex!("6bb2de380eefd199ee44364902f4b89679b8df04024e24047dc0ec1c61c11b2d527495fed4a6903ebe086e831bd000bf683c2a36f0454eaccaecf109678fd33e"),
491    )]
492    fn verify_fail(
493        #[case] public_key: impl Into<P256CompressedPublicKey>,
494        #[case] prehash: [u8; 32],
495        #[case] signature: impl Into<P256Signature>,
496    ) {
497        assert!(
498            !P256::verify(
499                &public_key.into().try_into().unwrap(),
500                &prehash,
501                &signature.into().try_into().unwrap(),
502            ),
503            "invalid signature passed verification",
504        );
505    }
506
507    #[rstest]
508    #[case(
509        hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"),
510    )]
511    #[case(
512        hex!("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"),
513    )]
514    #[tokio::test]
515    async fn sign_verify(#[case] prehash: [u8; 32]) {
516        test_sign_verify(SigningKey::generate(), prehash).await;
517    }
518}