RosettaCodeData/Task/Elliptic-curve-arithmetic/Rust/elliptic-curve-arithmetic.rs
2025-08-11 18:05:26 -07:00

211 lines
5.5 KiB
Rust

use std::fmt;
use std::ops::{Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Neg};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct EllipticPoint {
x: f64,
y: f64,
}
impl EllipticPoint {
const ZERO_THRESHOLD: f64 = 1e20;
const B: f64 = 7.0; // the 'b' in y^2 = x^3 + a * x + b, where 'a' is 0
/// Create a point that is initialized to Zero (point at infinity)
pub fn new() -> Self {
Self {
x: 0.0,
y: Self::ZERO_THRESHOLD * 1.01,
}
}
/// Create a point based on the y-coordinate. For a curve with a = 0 and b = 7
/// there is only one x for each y
pub fn from_y(y_coordinate: f64) -> Self {
let y = y_coordinate;
let x = (y * y - Self::B).cbrt();
Self { x, y }
}
/// Check if the point is zero (point at infinity)
pub fn is_zero(&self) -> bool {
// when the elliptic point is at zero, y = +/- infinity
self.y.abs() >= Self::ZERO_THRESHOLD
}
/// Double the point (multiply by 2)
fn double(&mut self) {
if self.is_zero() {
// doubling zero is still zero
return;
}
// based on the property of the curve, the line going through the
// current point and the negative doubled point is tangent to the
// curve at the current point. wikipedia has a nice diagram.
if self.y == 0.0 {
// at this point the tangent to the curve is vertical.
// this point doubled is 0
*self = EllipticPoint::new();
} else {
let l = (3.0 * self.x * self.x) / (2.0 * self.y);
let new_x = l * l - 2.0 * self.x;
self.y = l * (self.x - new_x) - self.y;
self.x = new_x;
}
}
}
impl Default for EllipticPoint {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for EllipticPoint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_zero() {
write!(f, "(Zero)")
} else {
write!(f, "({}, {})", self.x, self.y)
}
}
}
impl Neg for EllipticPoint {
type Output = Self;
fn neg(self) -> Self::Output {
Self {
x: self.x,
y: -self.y,
}
}
}
impl Add for EllipticPoint {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
let mut result = self;
result += rhs;
result
}
}
impl AddAssign for EllipticPoint {
fn add_assign(&mut self, rhs: Self) {
if self.is_zero() {
*self = rhs;
} else if rhs.is_zero() {
// since rhs is zero this point does not need to be modified
} else {
let l = (rhs.y - self.y) / (rhs.x - self.x);
if l.is_finite() {
let new_x = l * l - self.x - rhs.x;
self.y = l * (self.x - new_x) - self.y;
self.x = new_x;
} else {
if self.y.is_sign_negative() != rhs.y.is_sign_negative() {
// in this case rhs == -lhs, the result should be 0
*self = EllipticPoint::new();
} else {
// in this case rhs == lhs.
self.double();
}
}
}
}
}
impl Sub for EllipticPoint {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
self + (-rhs)
}
}
impl SubAssign for EllipticPoint {
fn sub_assign(&mut self, rhs: Self) {
*self += -rhs;
}
}
impl Mul<i32> for EllipticPoint {
type Output = Self;
fn mul(self, rhs: i32) -> Self::Output {
let mut result = self;
result *= rhs;
result
}
}
impl Mul<EllipticPoint> for i32 {
type Output = EllipticPoint;
fn mul(self, rhs: EllipticPoint) -> Self::Output {
rhs * self
}
}
impl MulAssign<i32> for EllipticPoint {
fn mul_assign(&mut self, mut rhs: i32) {
let mut r = EllipticPoint::new();
let mut p = *self;
if rhs < 0 {
// change p * -rhs to -p * rhs
rhs = -rhs;
p = -p;
}
let mut i = 1;
while i <= rhs {
if (i & rhs) != 0 {
r += p;
}
p.double();
i <<= 1;
}
*self = r;
}
}
fn main() {
let a = EllipticPoint::from_y(1.0);
let b = EllipticPoint::from_y(2.0);
println!("a = {}", a);
println!("b = {}", b);
let c = a + b;
println!("c = a + b = {}", c);
println!("a + b - c = {}", a + b - c);
println!("a + b - (b + a) = {}\n", a + b - (b + a));
println!("a + a + a + a + a - 5 * a = {}", a + a + a + a + a - 5 * a);
println!("a * 12345 = {}", a * 12345);
println!("a * -12345 = {}", a * -12345);
println!("a * 12345 + a * -12345 = {}", a * 12345 + a * -12345);
println!("a * 12345 - (a * 12000 + a * 345) = {}", a * 12345 - (a * 12000 + a * 345));
println!("a * 12345 - (a * 12001 + a * 345) = {}\n", a * 12345 - (a * 12000 + a * 344));
let zero = EllipticPoint::new();
let mut g = EllipticPoint::new();
println!("g = zero = {}", g);
g += a;
println!("g += a = {}", g);
g += zero;
println!("g += zero = {}", g);
g += b;
println!("g += b = {}", g);
println!("b + b - b * 2 = {}\n", b + b - b * 2);
let mut special = EllipticPoint::from_y(0.0); // the point where the curve crosses the x-axis
println!("special = {}", special); // this has the minimum possible value for x
special *= 2;
println!("special *= 2 = {}", special); // doubling it gives zero
}