1use getrandom::SysRng;
14use sm2::SecretKey;
15use sm2::elliptic_curve::Generate;
16use sm2::elliptic_curve::sec1::ToSec1Point;
17use sm2::pke::{DecryptingKey, EncryptingKey};
18
19use crate::contract::{AsymmetricCipher, Identified};
20use crate::error::{Error, Result};
21use crate::internal::{hex_decode, hex_encode};
22
23const LABEL: &str = "SM2";
24
25#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct KeyPairHex {
28 pub private_key_hex: String,
29 pub public_key_hex: String,
30}
31
32#[derive(Debug, Default, Clone, Copy)]
34pub struct Sm2Service;
35
36impl Sm2Service {
37 pub fn encrypt(plaintext: &[u8], public_key_hex: &str) -> Result<String> {
39 let encrypting_key = parse_public_key(public_key_hex)?;
40 let ciphertext = encrypting_key
41 .encrypt(&mut SysRng, plaintext)
42 .map_err(|_| Error::EncryptionFailed { label: LABEL })?;
43 Ok(hex_encode(&ciphertext))
44 }
45
46 pub fn decrypt(ciphertext_hex: &str, private_key_hex: &str) -> Result<Vec<u8>> {
48 let decrypting_key = DecryptingKey::new(parse_private_key(private_key_hex)?);
49 let ciphertext =
50 hex_decode(ciphertext_hex).map_err(|_| Error::DecryptionFailed { label: LABEL })?;
51 decrypting_key
52 .decrypt(&ciphertext)
53 .map_err(|_| Error::DecryptionFailed { label: LABEL })
54 }
55
56 pub fn generate_key_pair_hex() -> Result<KeyPairHex> {
58 let secret_key = SecretKey::try_generate_from_rng(&mut SysRng)
59 .map_err(|_| Error::EncryptionFailed { label: LABEL })?;
60 let public_key = secret_key.public_key();
61
62 Ok(KeyPairHex {
63 private_key_hex: hex_encode(secret_key.to_bytes().as_slice()),
64 public_key_hex: hex_encode(public_key.to_sec1_point(false).as_bytes()),
65 })
66 }
67}
68
69#[derive(Debug, Default, Clone, Copy)]
71pub struct Sm2AsymmetricCipher;
72
73impl Sm2AsymmetricCipher {
74 pub const IDENTIFIER: &'static str = "sm2";
75
76 pub fn new() -> Self {
77 Self
78 }
79}
80
81impl Identified for Sm2AsymmetricCipher {
82 fn identifier(&self) -> &str {
83 Self::IDENTIFIER
84 }
85}
86
87impl AsymmetricCipher for Sm2AsymmetricCipher {
88 fn encrypt(&self, plaintext: &[u8], public_key_hex: &str) -> Result<String> {
89 Sm2Service::encrypt(plaintext, public_key_hex)
90 }
91
92 fn decrypt(&self, ciphertext_hex: &str, private_key_hex: &str) -> Result<Vec<u8>> {
93 Sm2Service::decrypt(ciphertext_hex, private_key_hex)
94 }
95}
96
97fn parse_public_key(public_key_hex: &str) -> Result<EncryptingKey> {
99 let bytes = hex_decode(public_key_hex).map_err(|_| Error::InvalidKey { label: LABEL })?;
100
101 let sec1 = match bytes.len() {
102 65 | 33 => bytes,
103 64 => {
104 let mut normalized = Vec::with_capacity(65);
106 normalized.push(0x04);
107 normalized.extend_from_slice(&bytes);
108 normalized
109 }
110 _ => return Err(Error::InvalidKey { label: LABEL }),
111 };
112
113 EncryptingKey::from_sec1_bytes(&sec1).map_err(|_| Error::InvalidKey { label: LABEL })
114}
115
116fn parse_private_key(private_key_hex: &str) -> Result<SecretKey> {
118 let bytes = hex_decode(private_key_hex).map_err(|_| Error::InvalidKey { label: LABEL })?;
119 SecretKey::from_slice(&bytes).map_err(|_| Error::InvalidKey { label: LABEL })
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn keypair_roundtrip_and_hex_shapes() {
128 let pair = Sm2Service::generate_key_pair_hex().unwrap();
129 assert_eq!(pair.private_key_hex.len(), 64);
130 assert_eq!(pair.public_key_hex.len(), 130);
131 assert!(pair.public_key_hex.starts_with("04"));
132
133 let ciphertext =
134 Sm2Service::encrypt("国密数据 🎉".as_bytes(), &pair.public_key_hex).unwrap();
135 let plaintext = Sm2Service::decrypt(&ciphertext, &pair.private_key_hex).unwrap();
136 assert_eq!(plaintext, "国密数据 🎉".as_bytes());
137 }
138
139 #[test]
140 fn bare_xy_public_key_is_accepted_too() {
141 let pair = Sm2Service::generate_key_pair_hex().unwrap();
142 let bare = &pair.public_key_hex[2..]; let ciphertext = Sm2Service::encrypt(b"payload", bare).unwrap();
145 let plaintext = Sm2Service::decrypt(&ciphertext, &pair.private_key_hex).unwrap();
146 assert_eq!(plaintext, b"payload");
147 }
148
149 #[test]
150 fn wrong_private_key_or_tampered_ciphertext_fails() {
151 let pair = Sm2Service::generate_key_pair_hex().unwrap();
152 let other = Sm2Service::generate_key_pair_hex().unwrap();
153 let ciphertext = Sm2Service::encrypt(b"secret", &pair.public_key_hex).unwrap();
154
155 assert_eq!(
156 Sm2Service::decrypt(&ciphertext, &other.private_key_hex).unwrap_err(),
157 Error::DecryptionFailed { label: LABEL }
158 );
159
160 let mut tampered = ciphertext.clone();
161 let last = tampered.len() - 1;
162 let flipped = if tampered.as_bytes()[last] == b'0' {
163 '1'
164 } else {
165 '0'
166 };
167 tampered.replace_range(last.., &flipped.to_string());
168 assert_eq!(
169 Sm2Service::decrypt(&tampered, &pair.private_key_hex).unwrap_err(),
170 Error::DecryptionFailed { label: LABEL }
171 );
172 }
173
174 #[test]
175 fn malformed_keys_are_rejected() {
176 let pair = Sm2Service::generate_key_pair_hex().unwrap();
177
178 assert_eq!(
179 Sm2Service::decrypt("00", "zz").unwrap_err(),
180 Error::InvalidKey { label: LABEL }
181 );
182 assert_eq!(
183 Sm2Service::encrypt(b"x", "abcd").unwrap_err(),
184 Error::InvalidKey { label: LABEL }
185 );
186 let mut longer = pair.private_key_hex.clone();
188 longer.push('a');
189 assert_eq!(
190 Sm2Service::decrypt("00", &longer).unwrap_err(),
191 Error::InvalidKey { label: LABEL }
192 );
193 }
194
195 #[test]
196 fn cipher_trait_matches_service() {
197 let cipher = Sm2AsymmetricCipher::new();
198 assert_eq!(cipher.identifier(), "sm2");
199
200 let pair = Sm2Service::generate_key_pair_hex().unwrap();
201 let ciphertext = cipher.encrypt(b"via trait", &pair.public_key_hex).unwrap();
202 assert_eq!(
203 cipher.decrypt(&ciphertext, &pair.private_key_hex).unwrap(),
204 b"via trait"
205 );
206 }
207}