T H I C C_C U R V E S pt. 2

In part 1 we saw a basic explaination of the math behind elliptical curves (and some python code). In this part I’ll explain the ECDSA algorithm (Elliptical Curve Digital Signature Algorithm).

Key generation

The keys are based on Diffie-Hellman. It’s actually really simple:

def generate_keypair(self): # Diffie-Hellman
        self.private = randint(1, self.baseorder)
        self.public = self.multiply(self.private, self.base)

A.session_key = A.private*(B.public*base)
B.session_key = B.private*(A.public*base)

This works, because of the abelian property associativeness (maybe an english word?).

a = A.private
b = B.private
A.session_key = a*(b*base) = (a*b)*base = b*(a*base) = B.session_key

ECDSA algorithm

Wikipedia actually explains the algorithm very well, and why it works. Take a look.

Python code for the algorithm look like this:

def sign(self, H):
        z = int("0x"+H, 16)
        k = randint(1,self.baseorder-1)
        P = self.multiply(k,self.base)
        
        if P.x == 0:
            return self.sign(H)
        
        r = P.x
        s = (z+r*self.private)*I_invert(k, self.baseorder)%self.baseorder

        return int(r), int(s)

    def verify_signature(self, signature, pub, H):
        r,s = signature
        if not (1 <= r <= self.baseorder-1 and 1 <= s <= self.baseorder-1):
            raise Exception("Invalid signature")

        z = int("0x"+H, 16)
        u1 = z*I_invert(s,self.baseorder)%self.baseorder
        u2 = r*I_invert(s,self.baseorder)%self.baseorder

        P = self.add(self.multiply(u1,self.base),self.multiply(u2, pub))
        
        if r == P.x:
            return True
        else:
            raise Exception("Invalid signature")

Since I programmed it, I can decide the hashing algorithm, and therefore z. I chose sha1 with regards to the length of the hash.

The complete code can be found at github.

Rust implementation

So I’ve spent the last day to learn basic Rust and implementing ECDSA. Therefore my implementation may be relativaly bad from a speed perspective. But I think it turned out okay for my first time writing Rust.

Rust by default do not support the sizes of our parameters, therefore I needed to implement a big-num library (or crate as Rust calls it).

Rust does not support classes either, but like Go, it supports structs. I think this is fine, but it did change my implementation a bit. It now deviates a bit from my Python implementation.

Here’s the sign and verify_sign functions.

fn sign(h: &str, private: Integer) -> (Integer, Integer) {
    let base = Point{               
        x: hex2int("188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012"),
        y: hex2int("07192b95ffc8da78631011ed6b24cdd573f977a11e794811"),
    };
    let baseorder = hex2int("ffffffffffffffffffffffff99def836146bc9b1b4d22831");

    let z = hex2int(h);
    let k = gen_random();
    let P = point_multiply(&k, &base);
    
    if P.x == Integer::from(0) {
        sign(h, private.clone());
    }

    let r = P.x;
    let s = (z+r.clone()*private)*Integer::from(k.invert_ref(&baseorder).unwrap()).rem_floor(&baseorder);

    (r,s)
}

fn verify_signature(signature: (Integer, Integer), public: Point, h: &str) -> bool {
    let base = Point{               
        x: hex2int("188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012"),
        y: hex2int("07192b95ffc8da78631011ed6b24cdd573f977a11e794811"),
    };
    let baseorder = hex2int("ffffffffffffffffffffffff99def836146bc9b1b4d22831");

    let r = signature.0;
    let s = signature.1;
    
    if 1 > r && r > baseorder.clone()-Integer::from(1) && 1 > s && s > baseorder.clone()-Integer::from(1) {
        panic!("Invalid signature");
    }
    
    let z = hex2int(h);
    let u1 = z*Integer::from(s.invert_ref(&baseorder).unwrap()).rem_floor(&baseorder);
    let u2 = r.clone()*Integer::from(s.invert_ref(&baseorder).unwrap()).rem_floor(&baseorder);

    let P = point_add(&point_multiply(&u1, &base), &point_multiply(&u2, &public));
    
    if r == P.x {
      return true
    }

    return false
}

The complete code can be found at github.

Speed comparison

For each iteration I generate the keys, sign a message and verify the message. I do this 1000 times on each program.

# ec.py

for i in range(1000):
        ec = EC(a,b,G,n,p)

        ec.generate_keypair()
     
        H = "719609852b46b8ea9a5fcd39eb7bc9088fa36399"    
        r,s = ec.sign(H)
        ec.verify_signature((r,s), ec.public, H)

Here are the results.

$ time python ec.py

real	0m8,314s
user	0m7,358s
sys	0m0,033s
$ time ecRust/target/release/ec_rust

real	0m5,099s
user	0m5,097s
sys	0m0,000s

So Rust was a bit faster, but not by much. This is probably due to my bad Rust skills and only knowing it for about 24 hours :) I suspect that my implementation can be sped up by a factor or two.

Next time

My CAN-buses haven’t arrived yet, but when they do I will take a dive into CAN and some cool stuff!