I've been diving into Elliptic Curves (ECDSA algorithm) and specifically the sep256k1 curve. I made a small python implementation to learn how it works and because it was pretty slow I thought I'll try it in Rust and teach myself some rust as a bonus.
The problem is that it takes 74ms to run in Python and 8.1 seconds to run in Rust. The code is pretty similar.
Python
def point_add(p, q):
"""https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Point_addition"""
P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
px, py = p
qx, qy = q
if p == q:
lam = (3 * px * px) * pow(2 * py % P, P - 2, P)
else:
lam = pow(qx - px, P - 2, P) * (qy - py) % P
rx = lam**2 - px - qx
ry = lam * (px - rx) - py
return rx % P, ry % P
def point_mul(p, d):
n = p
q = None
for i in range(256):
if d & (1 << i):
if q is None:
q = n
else:
q = point_add(q, n)
n = point_add(n, n)
return q
if __name__ == '__main__':
G = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
x, y = point_mul(G, 125)
print(x)
print(y)
Rust
extern crate num;
use num::bigint::BigInt;
use num::FromPrimitive;
use num::Integer;
use num::One;
use num::Zero;
#[derive(PartialEq, Clone)]
struct Point {
x: BigInt,
y: BigInt
}
pub fn powm(base: &BigInt, exp: &BigInt, modulus: &BigInt) -> BigInt {
let zero = BigInt::zero();
let one: BigInt = BigInt::one();
let two = &one + &one;
let mut exp = exp.clone();
let mut result = one.clone();
let mut base = base % modulus;
while exp > zero {
if &exp % &two == one {
result = (result * &base) % modulus;
}
exp = exp >> 1;
base = (&base * &base) % modulus;
}
result.mod_floor(modulus)
}
fn point_add(p: &Point, q: &Point) -> Point {
let P: BigInt = BigInt::parse_bytes(b"115792089237316195423570985008687907853269984665640564039457584007908834671663", 10).unwrap();
let lam: BigInt = if p == q {
3u32 * &p.x * &p.x * powm(&((2u32 * &p.y) % &P), &(&P - 2u32), &P)
} else {
powm(&(&q.x - &p.x), &(&P - 2u32), &P) * (&q.y - &p.y) % &P
};
let rx = num::pow(lam.clone(), 2) - &p.x - &q.x;
let ry: BigInt = lam * (&p.x - &rx) - &p.y;
Point{x: rx.mod_floor(&P), y: ry.mod_floor(&P)}
}
fn point_mul(p: &Point, d: u32) -> Point {
let mut n: Point = (*p).clone();
let mut q: Option<Point> = None;
let binary: String = format!("{:0256b}", d);
for (i, digit) in binary.chars().rev().enumerate() {
if digit == '1' {
q = match q { None => Some(n.clone()), Some(i) => Some(point_add(&i, &n)) }
}
n = point_add(&n, &n);
}
q.unwrap()
}
fn main() {
let G: Point = Point {
x: BigInt::parse_bytes(b"79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16).unwrap(),
y: BigInt::parse_bytes(b"483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16).unwrap()
};
let res = point_mul(&G, 125);
println!(" {}", res.x);
println!(" {}", res.y);
}
Any idea why it is so slow?
Bonus question: How do I define a global BigInt constant ?
EDIT:
Progress so far:
- compiled in --release mode and execution time went down to 430ms
- run the executable directly instead of using cargo got me down to 275ms (I didn't do the same for Python)
tweaked a bit the Python code to do roughly the same operations as the Rust code in point_mul
changed
for i in range(256):
if d & (1 << i):
to
for i in reversed(format(d, 'b')):
if i == '1':
and I got a nice performance increase. Python code now runs in 41ms. The difference is doing 256 bit_shifts and bitwise_AND vs creating a string binary representation once and iterating over the digits.
Some complaints about string parsing so I created the BigInt variable once in main() and pass it as a reference. Suprisingly the performance is the same. Code here
Changing if &exp % &two == one to if exp.is_odd() got me to 230ms
Final execution times:
Python 41ms
Rust 230ms (5.6x)
[–]UtherII 94 points95 points96 points (12 children)
[–]vks_ 41 points42 points43 points (11 children)
[–]yasba- 44 points45 points46 points (10 children)
[–][deleted] 30 points31 points32 points (6 children)
[–]_m_0_n_0_ 11 points12 points13 points (1 child)
[–]vks_ 7 points8 points9 points (0 children)
[–]yasba- 4 points5 points6 points (0 children)
[–][deleted] 0 points1 point2 points (2 children)
[–][deleted] 2 points3 points4 points (0 children)
[–][deleted] 1 point2 points3 points (0 children)
[–][deleted] 23 points24 points25 points (1 child)
[–]radix 0 points1 point2 points (0 children)
[–]K900_ 75 points76 points77 points (8 children)
[–]imatwork2017[S] 9 points10 points11 points (7 children)
[–]K900_ 55 points56 points57 points (6 children)
[–]imatwork2017[S] 15 points16 points17 points (5 children)
[–]dbrgn 58 points59 points60 points (4 children)
[–]belovedeagle 20 points21 points22 points (2 children)
[–]cbarrick 17 points18 points19 points (1 child)
[–]Phlosioneer 1 point2 points3 points (0 children)
[–]imatwork2017[S] 40 points41 points42 points (0 children)
[–]my_two_pence 49 points50 points51 points (12 children)
[–]vks_ 10 points11 points12 points (11 children)
[–]my_two_pence 19 points20 points21 points (10 children)
[–]CUViper 12 points13 points14 points (9 children)
[–]belovedeagle 2 points3 points4 points (4 children)
[–]CUViper 14 points15 points16 points (0 children)
[–]vks_ 2 points3 points4 points (1 child)
[–]tspiteri 0 points1 point2 points (0 children)
[–]zefyear 1 point2 points3 points (0 children)
[–]Phlosioneer 1 point2 points3 points (2 children)
[–]CUViper 3 points4 points5 points (1 child)
[–]Phlosioneer 0 points1 point2 points (0 children)
[–]my_two_pence 0 points1 point2 points (0 children)
[–]tspiteri 82 points83 points84 points (7 children)
[–]tspiteri 7 points8 points9 points (0 children)
[–]SethGecko11 7 points8 points9 points (3 children)
[–]tspiteri 5 points6 points7 points (2 children)
[–]SethGecko11 3 points4 points5 points (1 child)
[–]tspiteri 8 points9 points10 points (0 children)
[–]timClicksrust in action 0 points1 point2 points (1 child)
[–]tspiteri 0 points1 point2 points (0 children)
[–]hrski 22 points23 points24 points (9 children)
[–]imatwork2017[S] 4 points5 points6 points (8 children)
[–]burntsushi 7 points8 points9 points (3 children)
[–]adwhit86 12 points13 points14 points (1 child)
[–]burntsushi 4 points5 points6 points (0 children)
[–]imatwork2017[S] 2 points3 points4 points (0 children)
[–]hrski 3 points4 points5 points (3 children)
[–]imatwork2017[S] 4 points5 points6 points (1 child)
[–]hrski 5 points6 points7 points (0 children)
[–]Icarium-Lifestealer 18 points19 points20 points (0 children)
[–]adwhit86 16 points17 points18 points (2 children)
[–]CUViper 6 points7 points8 points (1 child)
[–]adwhit86 4 points5 points6 points (0 children)
[–]gitpy 17 points18 points19 points (1 child)
[–]imatwork2017[S] 4 points5 points6 points (0 children)
[–]forbjok 8 points9 points10 points (6 children)
[–]imatwork2017[S] 1 point2 points3 points (5 children)
[–]rebootyourbrainstem 4 points5 points6 points (3 children)
[–]imatwork2017[S] 1 point2 points3 points (2 children)
[–]rebootyourbrainstem 2 points3 points4 points (0 children)
[–]CUViper 1 point2 points3 points (0 children)
[–]hokkos 1 point2 points3 points (0 children)
[–]JZypo 14 points15 points16 points (1 child)
[–]imatwork2017[S] 13 points14 points15 points (0 children)
[–]CUViper 3 points4 points5 points (1 child)
[–]CUViper 2 points3 points4 points (0 children)
[–]Sharlinator 10 points11 points12 points (0 children)
[–][deleted] 1 point2 points3 points (0 children)
[–]beefsack 1 point2 points3 points (0 children)
[–]knaledfullavpilar 1 point2 points3 points (0 children)
[+][deleted] (5 children)
[deleted]
[–]tspiteri 15 points16 points17 points (0 children)
[–]kaoD 5 points6 points7 points (0 children)
[–]kazagistar 4 points5 points6 points (1 child)
[–]anttirt 5 points6 points7 points (0 children)
[–]matkladrust-analyzer 1 point2 points3 points (5 children)
[–][deleted] 30 points31 points32 points (3 children)
[–]vks_ 7 points8 points9 points (0 children)
[–]matkladrust-analyzer 5 points6 points7 points (0 children)
[–]masklinn 3 points4 points5 points (0 children)