1use core::fmt;
27
28use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
29
30use crate::curve_hessian::HessianCurve;
31use crate::point_ops::{PointAdd, PointOps};
32use fp::field_ops::FieldOps;
33
34#[derive(Debug, Clone, Copy)]
36pub struct HessianPoint<F: FieldOps> {
37 pub x: F,
39 pub y: F,
41 pub z: F,
43}
44
45impl<F> fmt::Display for HessianPoint<F>
46where
47 F: FieldOps + fmt::Display,
48{
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 if self.is_identity() {
51 if f.alternate() {
52 write!(f, "HessianPoint {{ O = (1:-1:0) }}")
53 } else {
54 write!(f, "O")
55 }
56 } else if self.is_zero_projective() {
57 if f.alternate() {
58 write!(f, "HessianPoint {{ invalid = (0:0:0) }}")
59 } else {
60 write!(f, "(0:0:0)")
61 }
62 } else if let Some((x_aff, y_aff)) = self.to_affine() {
63 if f.alternate() {
64 write!(
65 f,
66 "HessianPoint {{ X:Y:Z = ({}:{}:{}), x = {}, y = {} }}",
67 self.x, self.y, self.z, x_aff, y_aff
68 )
69 } else {
70 write!(f, "({}, {})", x_aff, y_aff)
71 }
72 } else if f.alternate() {
73 write!(f, "HessianPoint {{ X:Y:Z = ({}:{}:{}) }}", self.x, self.y, self.z)
74 } else {
75 write!(f, "({}:{}:{})", self.x, self.y, self.z)
76 }
77 }
78}
79
80impl<F: FieldOps> PartialEq for HessianPoint<F> {
81 fn eq(&self, other: &Self) -> bool {
83 let self_zero = self.is_zero_projective();
84 let other_zero = other.is_zero_projective();
85 if self_zero || other_zero {
86 return self_zero && other_zero;
87 }
88
89 self.x * other.y == other.x * self.y
90 && self.x * other.z == other.x * self.z
91 && self.y * other.z == other.y * self.z
92 }
93}
94
95impl<F: FieldOps> Eq for HessianPoint<F> {}
96
97impl<F: FieldOps> HessianPoint<F> {
98 pub fn new(x: F, y: F, z: F) -> Self {
100 Self { x, y, z }
101 }
102
103 pub fn from_affine(x: F, y: F) -> Self {
105 Self { x, y, z: F::one() }
106 }
107
108 pub fn identity() -> Self {
110 Self {
111 x: F::one(),
112 y: -F::one(),
113 z: F::zero(),
114 }
115 }
116
117 pub fn is_identity(&self) -> bool {
119 if !bool::from(self.z.is_zero()) {
120 return false;
121 }
122
123 if bool::from(self.x.is_zero()) && bool::from(self.y.is_zero()) {
124 return false;
125 }
126
127 self.x + self.y == F::zero()
128 }
129
130 pub fn is_at_infinity(&self) -> bool {
132 bool::from(self.z.is_zero()) && !self.is_zero_projective()
133 }
134
135 pub fn is_zero_projective(&self) -> bool {
137 bool::from(self.x.is_zero())
138 && bool::from(self.y.is_zero())
139 && bool::from(self.z.is_zero())
140 }
141
142 pub fn to_affine(&self) -> Option<(F, F)> {
144 self.z
145 .invert()
146 .into_option()
147 .map(|zinv| (self.x * zinv, self.y * zinv))
148 }
149}
150
151impl<F> ConditionallySelectable for HessianPoint<F>
152where
153 F: FieldOps + Copy,
154{
155 fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
156 Self {
157 x: F::conditional_select(&a.x, &b.x, choice),
158 y: F::conditional_select(&a.y, &b.y, choice),
159 z: F::conditional_select(&a.z, &b.z, choice),
160 }
161 }
162
163 fn conditional_assign(&mut self, other: &Self, choice: Choice) {
164 self.x.conditional_assign(&other.x, choice);
165 self.y.conditional_assign(&other.y, choice);
166 self.z.conditional_assign(&other.z, choice);
167 }
168
169 fn conditional_swap(a: &mut Self, b: &mut Self, choice: Choice) {
170 F::conditional_swap(&mut a.x, &mut b.x, choice);
171 F::conditional_swap(&mut a.y, &mut b.y, choice);
172 F::conditional_swap(&mut a.z, &mut b.z, choice);
173 }
174}
175
176impl<F> ConstantTimeEq for HessianPoint<F>
177where
178 F: FieldOps + Copy + ConstantTimeEq,
179{
180 fn ct_eq(&self, other: &Self) -> Choice {
181 let self_zero = self.is_zero_projective();
182 let other_zero = other.is_zero_projective();
183 if self_zero || other_zero {
184 return Choice::from((self_zero && other_zero) as u8);
185 }
186
187 (self.x * other.y).ct_eq(&(other.x * self.y))
188 & (self.x * other.z).ct_eq(&(other.x * self.z))
189 & (self.y * other.z).ct_eq(&(other.y * self.z))
190 }
191
192 fn ct_ne(&self, other: &Self) -> Choice {
193 !self.ct_eq(other)
194 }
195}
196
197impl<F: FieldOps> HessianPoint<F> {
198 pub fn negate(&self, _curve: &HessianCurve<F>) -> Self {
202 Self::new(self.y, self.x, self.z)
203 }
204
205 pub fn double(&self, curve: &HessianCurve<F>) -> Self {
215 if self.is_identity() {
216 return *self;
217 }
218
219 let x2 = <F as FieldOps>::square(&self.x);
220 let y2 = <F as FieldOps>::square(&self.y);
221 let z2 = <F as FieldOps>::square(&self.z);
222
223 let x3 = self.x * x2;
224 let y3 = self.y * y2;
225 let z3 = self.z * z2;
226 let cz3 = curve.c * z3;
227
228 Self::new(
229 self.y * (cz3 - x3),
230 self.x * (y3 - cz3),
231 self.z * (x3 - y3),
232 )
233 }
234
235 fn add_formula_9(&self, other: &Self, curve: &HessianCurve<F>) -> Self {
237 let x1_sq = <F as FieldOps>::square(&self.x);
238 let y1_sq = <F as FieldOps>::square(&self.y);
239 let z1_sq = <F as FieldOps>::square(&self.z);
240 let x2_sq = <F as FieldOps>::square(&other.x);
241 let y2_sq = <F as FieldOps>::square(&other.y);
242 let z2_sq = <F as FieldOps>::square(&other.z);
243
244 let x3 = curve.c * other.y * other.z * z1_sq - self.x * self.y * x2_sq;
245 let y3 = other.x * other.y * y1_sq - curve.c * self.x * self.z * z2_sq;
246 let z3 = other.x * other.z * x1_sq - self.y * self.z * y2_sq;
247
248 Self::new(x3, y3, z3)
249 }
250
251 fn add_formula_10(&self, other: &Self, curve: &HessianCurve<F>) -> Self {
253 let x1_sq = <F as FieldOps>::square(&self.x);
254 let y1_sq = <F as FieldOps>::square(&self.y);
255 let z1_sq = <F as FieldOps>::square(&self.z);
256 let x2_sq = <F as FieldOps>::square(&other.x);
257 let y2_sq = <F as FieldOps>::square(&other.y);
258 let z2_sq = <F as FieldOps>::square(&other.z);
259
260 let x3 = curve.c * self.y * self.z * z2_sq - other.x * other.y * x1_sq;
261 let y3 = self.x * self.y * y2_sq - curve.c * other.x * other.z * z1_sq;
262 let z3 = self.x * self.z * x2_sq - other.y * other.z * y1_sq;
263
264 Self::new(x3, y3, z3)
265 }
266
267 pub fn add(&self, other: &Self, curve: &HessianCurve<F>) -> Self {
273 if self.is_identity() {
274 return *other;
275 }
276 if other.is_identity() {
277 return *self;
278 }
279
280 let r = self.add_formula_9(other, curve);
281 if !r.is_zero_projective() {
282 return r;
283 }
284
285 let s = self.add_formula_10(other, curve);
286 if !s.is_zero_projective() {
287 return s;
288 }
289
290 if *other == self.negate(curve) {
291 return Self::identity();
292 }
293
294 panic!("Hessian addition failed for valid-looking inputs; both unified formula branches vanished");
295 }
296
297 pub fn scalar_mul(&self, k: &[u64], curve: &HessianCurve<F>) -> Self {
299 let mut result = Self::identity();
300
301 for &limb in k.iter().rev() {
302 for bit in (0..64).rev() {
303 let doubled = result.double(curve);
304 let added = doubled.add(self, curve);
305 let choice = Choice::from(((limb >> bit) & 1) as u8);
306 result = Self::conditional_select(&doubled, &added, choice);
307 }
308 }
309
310 result
311 }
312}
313
314impl<F: FieldOps> PointOps for HessianPoint<F> {
315 type BaseField = F;
316 type Curve = HessianCurve<F>;
317
318 fn identity(_curve: &Self::Curve) -> Self {
319 HessianPoint::<F>::identity()
320 }
321
322 fn is_identity(&self) -> bool {
323 HessianPoint::<F>::is_identity(self)
324 }
325
326 fn negate(&self, curve: &Self::Curve) -> Self {
327 HessianPoint::<F>::negate(self, curve)
328 }
329
330 fn scalar_mul(&self, k: &[u64], curve: &Self::Curve) -> Self {
331 HessianPoint::<F>::scalar_mul(self, k, curve)
332 }
333}
334
335impl<F: FieldOps> PointAdd for HessianPoint<F> {
336 fn add(&self, other: &Self, curve: &Self::Curve) -> Self {
337 HessianPoint::<F>::add(self, other, curve)
338 }
339}