1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
//! SM2 public keys.
use crate::sec1::{SEC1_UNCOMPRESSED_LEN, decode_uncompressed_point, encode_uncompressed_point};
use crate::sm2::point::ProjectivePoint;
use subtle::ConstantTimeEq;
/// SM2 public key: a curve point `P = d·G`.
#[derive(Clone, Copy, Debug)]
pub struct Sm2PublicKey {
point: ProjectivePoint,
}
impl Sm2PublicKey {
/// Wrap a curve point as a public key. Caller is responsible for
/// having checked the point is on-curve and not at infinity. API entry
/// points that need stronger failure guarantees perform their own
/// boundary checks.
#[must_use]
pub const fn from_point(point: ProjectivePoint) -> Self {
Self { point }
}
/// Underlying point.
#[must_use]
pub const fn point(&self) -> ProjectivePoint {
self.point
}
/// Decode a SEC1 uncompressed public key (`04 || X || Y`, 65 bytes)
/// into an `Sm2PublicKey`. Rejects the identity point and any
/// off-curve `(X, Y)`. Returns `None` for any malformed input.
#[must_use]
pub fn from_sec1_bytes(bytes: &[u8]) -> Option<Self> {
let point = decode_uncompressed_point(bytes)?;
// decode_uncompressed_point already enforces on-curve and
// length=65. The identity point cannot be encoded as `04 || X
// || Y` (the identity has no affine form), so the on-curve
// check on (X, Y) implicitly excludes it. Be explicit anyway.
if bool::from(point.is_identity()) {
return None;
}
Some(Self { point })
}
/// Encode this public key as 65 bytes of SEC1 uncompressed
/// `04 || X || Y`. Panics only if the underlying point is at
/// infinity, which `from_sec1_bytes` and the [`crate::sm2::Sm2PrivateKey`]
/// constructor both rule out at the boundary.
///
/// # Panics
///
/// Panics if the underlying point is at infinity.
#[must_use]
#[allow(clippy::missing_panics_doc)]
pub fn to_sec1_uncompressed(&self) -> [u8; SEC1_UNCOMPRESSED_LEN] {
let (x, y) = self
.point
.to_affine()
.expect("Sm2PublicKey at infinity violates the invariant");
encode_uncompressed_point(&x, &y)
}
}
impl From<ProjectivePoint> for Sm2PublicKey {
fn from(p: ProjectivePoint) -> Self {
Self::from_point(p)
}
}
impl ConstantTimeEq for Sm2PublicKey {
fn ct_eq(&self, other: &Self) -> subtle::Choice {
self.point.ct_eq(&other.point)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sm2::Sm2PrivateKey;
use crypto_bigint::U256;
/// Round-trip the SM2 generator's public-point uncompressed encoding.
#[test]
fn sec1_round_trip_generator() {
let g = Sm2PublicKey::from_point(ProjectivePoint::generator());
let bytes = g.to_sec1_uncompressed();
let recovered = Sm2PublicKey::from_sec1_bytes(&bytes).expect("decode");
assert_eq!(bytes, recovered.to_sec1_uncompressed());
}
/// Round-trip the GB/T 32918.2 sample public key derived from D.
#[test]
fn sec1_round_trip_gbt_sample() {
let d =
U256::from_be_hex("3945208F7B2144B13F36E38AC6D39F95889393692860B51A42FB81EF4DF7C5B8");
let priv_key = Sm2PrivateKey::new(d).expect("valid d");
let pub_key = Sm2PublicKey::from_point(priv_key.public_key());
let bytes = pub_key.to_sec1_uncompressed();
assert_eq!(bytes[0], 0x04);
let recovered = Sm2PublicKey::from_sec1_bytes(&bytes).expect("decode");
assert!(bool::from(pub_key.ct_eq(&recovered)));
}
/// `from_sec1_bytes` rejects wrong length / wrong tag / off-curve.
#[test]
fn sec1_rejects_malformed() {
assert!(Sm2PublicKey::from_sec1_bytes(&[0x04]).is_none());
let mut bad = [0u8; 65];
bad[0] = 0x04;
bad[1] = 1;
bad[33] = 1;
assert!(Sm2PublicKey::from_sec1_bytes(&bad).is_none());
// Compressed form rejected.
bad[0] = 0x02;
assert!(Sm2PublicKey::from_sec1_bytes(&bad).is_none());
}
}