gmcrypto_core/sm2/
public_key.rs1use crate::sec1::{SEC1_UNCOMPRESSED_LEN, decode_uncompressed_point, encode_uncompressed_point};
4use crate::sm2::point::ProjectivePoint;
5use subtle::ConstantTimeEq;
6
7#[derive(Clone, Copy, Debug)]
9pub struct Sm2PublicKey {
10 point: ProjectivePoint,
11}
12
13impl Sm2PublicKey {
14 #[must_use]
19 pub const fn from_point(point: ProjectivePoint) -> Self {
20 Self { point }
21 }
22
23 #[must_use]
25 pub const fn point(&self) -> ProjectivePoint {
26 self.point
27 }
28
29 #[must_use]
33 pub fn from_sec1_bytes(bytes: &[u8]) -> Option<Self> {
34 let point = decode_uncompressed_point(bytes)?;
35 if bool::from(point.is_identity()) {
40 return None;
41 }
42 Some(Self { point })
43 }
44
45 #[must_use]
54 #[allow(clippy::missing_panics_doc)]
55 pub fn to_sec1_uncompressed(&self) -> [u8; SEC1_UNCOMPRESSED_LEN] {
56 let (x, y) = self
57 .point
58 .to_affine()
59 .expect("Sm2PublicKey at infinity violates the invariant");
60 encode_uncompressed_point(&x, &y)
61 }
62}
63
64impl From<ProjectivePoint> for Sm2PublicKey {
65 fn from(p: ProjectivePoint) -> Self {
66 Self::from_point(p)
67 }
68}
69
70impl ConstantTimeEq for Sm2PublicKey {
71 fn ct_eq(&self, other: &Self) -> subtle::Choice {
72 self.point.ct_eq(&other.point)
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79 use crate::sm2::Sm2PrivateKey;
80 use crypto_bigint::U256;
81
82 #[test]
84 fn sec1_round_trip_generator() {
85 let g = Sm2PublicKey::from_point(ProjectivePoint::generator());
86 let bytes = g.to_sec1_uncompressed();
87 let recovered = Sm2PublicKey::from_sec1_bytes(&bytes).expect("decode");
88 assert_eq!(bytes, recovered.to_sec1_uncompressed());
89 }
90
91 #[test]
93 fn sec1_round_trip_gbt_sample() {
94 let d =
95 U256::from_be_hex("3945208F7B2144B13F36E38AC6D39F95889393692860B51A42FB81EF4DF7C5B8");
96 let priv_key = Sm2PrivateKey::from_scalar_inner(d).expect("valid d");
97 let pub_key = Sm2PublicKey::from_point(priv_key.public_key());
98 let bytes = pub_key.to_sec1_uncompressed();
99 assert_eq!(bytes[0], 0x04);
100 let recovered = Sm2PublicKey::from_sec1_bytes(&bytes).expect("decode");
101 assert!(bool::from(pub_key.ct_eq(&recovered)));
102 }
103
104 #[test]
106 fn sec1_rejects_malformed() {
107 assert!(Sm2PublicKey::from_sec1_bytes(&[0x04]).is_none());
108 let mut bad = [0u8; 65];
109 bad[0] = 0x04;
110 bad[1] = 1;
111 bad[33] = 1;
112 assert!(Sm2PublicKey::from_sec1_bytes(&bad).is_none());
113 bad[0] = 0x02;
115 assert!(Sm2PublicKey::from_sec1_bytes(&bad).is_none());
116 }
117}