Skip to main content

defuse_crypto/
p256.rs

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