gmcrypto_core/sm2/
point.rs1use crate::sm2::curve::{Fp, GX_HEX, GY_HEX, b};
9use crypto_bigint::U256;
10use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
11
12#[derive(Clone, Copy, Debug)]
16pub struct ProjectivePoint {
17 pub(crate) x: Fp,
18 pub(crate) y: Fp,
19 pub(crate) z: Fp,
20}
21
22impl ConstantTimeEq for ProjectivePoint {
23 fn ct_eq(&self, other: &Self) -> Choice {
24 let lhs_x = self.x * other.z;
25 let rhs_x = other.x * self.z;
26 let lhs_y = self.y * other.z;
27 let rhs_y = other.y * self.z;
28 lhs_x.retrieve().ct_eq(&rhs_x.retrieve()) & lhs_y.retrieve().ct_eq(&rhs_y.retrieve())
29 }
30}
31
32impl ConditionallySelectable for ProjectivePoint {
33 fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
34 Self {
35 x: Fp::conditional_select(&a.x, &b.x, choice),
36 y: Fp::conditional_select(&a.y, &b.y, choice),
37 z: Fp::conditional_select(&a.z, &b.z, choice),
38 }
39 }
40}
41
42impl ProjectivePoint {
43 #[must_use]
45 pub const fn identity() -> Self {
46 Self {
47 x: Fp::new(&U256::ZERO),
48 y: Fp::new(&U256::ONE),
49 z: Fp::new(&U256::ZERO),
50 }
51 }
52
53 #[must_use]
55 pub const fn generator() -> Self {
56 Self {
57 x: Fp::new(&U256::from_be_hex(GX_HEX)),
58 y: Fp::new(&U256::from_be_hex(GY_HEX)),
59 z: Fp::new(&U256::ONE),
60 }
61 }
62
63 #[must_use]
65 pub fn is_identity(&self) -> Choice {
66 self.z.retrieve().ct_eq(&U256::ZERO)
67 }
68
69 #[must_use]
73 #[allow(clippy::similar_names)]
74 pub fn add(&self, other: &Self) -> Self {
75 let b = b();
76 let (x1, y1, z1) = (self.x, self.y, self.z);
77 let (x2, y2, z2) = (other.x, other.y, other.z);
78
79 let xx = x1 * x2; let yy = y1 * y2; let zz = z1 * z2; let xy_pairs = ((x1 + y1) * (x2 + y2)) - (xx + yy); let yz_pairs = ((y1 + z1) * (y2 + z2)) - (yy + zz); let xz_pairs = ((x1 + z1) * (x2 + z2)) - (xx + zz); let bzz_part = xz_pairs - b * zz; let bzz3_part = bzz_part + bzz_part + bzz_part; let yy_m_bzz3 = yy - bzz3_part; let yy_p_bzz3 = yy + bzz3_part; let zz3 = zz + zz + zz; let bxz_part = b * xz_pairs - (zz3 + xx); let bxz3_part = bxz_part + bxz_part + bxz_part; let xx3_m_zz3 = xx + xx + xx - zz3; Self {
97 x: (yy_p_bzz3 * xy_pairs) - (yz_pairs * bxz3_part), y: (yy_p_bzz3 * yy_m_bzz3) + (xx3_m_zz3 * bxz3_part), z: (yy_m_bzz3 * yz_pairs) + (xy_pairs * xx3_m_zz3), }
101 }
102
103 #[must_use]
108 #[allow(clippy::similar_names)]
109 pub fn double(&self) -> Self {
110 let b = b();
111 let (x, y, z) = (self.x, self.y, self.z);
112
113 let xx = x * x; let yy = y * y; let zz = z * z; let xy2 = (x * y) + (x * y); let xz2 = (x * z) + (x * z); let bzz_part = b * zz - xz2; let bzz3_part = bzz_part + bzz_part + bzz_part; let yy_m_bzz3 = yy - bzz3_part; let yy_p_bzz3 = yy + bzz3_part; let y_frag = yy_p_bzz3 * yy_m_bzz3; let x_frag = yy_m_bzz3 * xy2; let zz3 = zz + zz + zz; let bxz2_part = b * xz2 - (zz3 + xx); let bxz6_part = bxz2_part + bxz2_part + bxz2_part; let xx3_m_zz3 = xx + xx + xx - zz3; let y3 = y_frag + xx3_m_zz3 * bxz6_part; let yz2 = (y * z) + (y * z); let x3 = x_frag - bxz6_part * yz2; let z3_tmp = yz2 * yy; let z3_tmp2 = z3_tmp + z3_tmp; let z3 = z3_tmp2 + z3_tmp2; Self {
139 x: x3,
140 y: y3,
141 z: z3,
142 }
143 }
144
145 #[must_use]
147 pub fn neg(&self) -> Self {
148 Self {
149 x: self.x,
150 y: -self.y,
151 z: self.z,
152 }
153 }
154
155 #[must_use]
172 pub fn to_affine(&self) -> Option<(Fp, Fp)> {
173 let z_inv: subtle::CtOption<Fp> = self.z.invert().into();
174 let z_inv: Option<Fp> = z_inv.into();
175 let z_inv = z_inv?;
176 Some((self.x * z_inv, self.y * z_inv))
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use crate::sm2::curve::b;
184 use subtle::ConstantTimeEq;
185
186 #[test]
187 fn doubling_equals_self_addition() {
188 let g = ProjectivePoint::generator();
189 let g2_double = g.double();
190 let g2_add = g.add(&g);
191 assert!(
192 bool::from(g2_double.ct_eq(&g2_add)),
193 "doubling and self-addition must agree"
194 );
195 }
196
197 #[test]
198 fn add_with_identity_is_identity_law() {
199 let g = ProjectivePoint::generator();
200 let id = ProjectivePoint::identity();
201 let lhs = g.add(&id);
202 assert!(bool::from(lhs.ct_eq(&g)), "G + O = G");
203 }
204
205 #[test]
206 fn add_with_negation_is_identity() {
207 let g = ProjectivePoint::generator();
208 let neg_g = g.neg();
209 let sum = g.add(&neg_g);
210 assert!(bool::from(sum.is_identity()), "G + (-G) = O");
211 }
212
213 #[test]
217 fn two_g_known_affine() {
218 let g2 = ProjectivePoint::generator().double();
219 let (x, y) = g2.to_affine().expect("2G is not infinity");
220 assert_eq!(
221 x.retrieve(),
222 U256::from_be_hex("56CEFD60D7C87C000D58EF57FA73BA4D9C0DFA08C08A7331495C2E1DA3F2BD52")
223 );
224 assert_eq!(
225 y.retrieve(),
226 U256::from_be_hex("31B7E7E6CC8189F668535CE0F8EAF1BD6DE84C182F6C8E716F780D3A970A23C3")
227 );
228 }
229
230 #[test]
232 fn three_g_known_affine() {
233 let g = ProjectivePoint::generator();
234 let g3 = g.double().add(&g);
235 let (x, y) = g3.to_affine().expect("3G is not infinity");
236 assert_eq!(
237 x.retrieve(),
238 U256::from_be_hex("A97F7CD4B3C993B4BE2DAA8CDB41E24CA13F6BD945302244E26918F1D0509EBF")
239 );
240 assert_eq!(
241 y.retrieve(),
242 U256::from_be_hex("530B5DD88C688EF5CCC5CEC08A72150F7C400EE5CD045292AAACDD037458F6E6")
243 );
244 }
245
246 #[test]
247 fn to_affine_round_trip_via_double() {
248 let g = ProjectivePoint::generator();
249 let g2 = g.double();
250 let (x, y) = g2.to_affine().expect("2G is not at infinity");
251 let lhs = y * y;
252 let three = Fp::new(&U256::from_u64(3));
253 let rhs = x * x * x - three * x + b();
254 assert_eq!(
255 lhs.retrieve(),
256 rhs.retrieve(),
257 "2G affine coords must satisfy curve equation"
258 );
259 }
260}