Signature algorithm

  • Use the secret scalar ee to compute the public pointPP, by doing a scalar multiplication with GG: eG=PeG = P.

  • Pick a random (secret) scalar kk, and perform a scalar multiplication with GG to get a random pointRR. kG=RkG = R

  • Use the above variables in the general equation of ECDSA is: uG+vP=RuG + vP = R, where, u=z/su = z/s, v=r/sv=r/s

  • Simplify the resulting equation to get the ss component of the signature:

    s=(z+re)/ks = (z + re) / k

from random import randint
class PrivateKey:
secret: int
def sign(self, z: int) -> Signature:
e = self.secret
k = randint(0, N)
R = k * G
r = R.x.value
k_inv = pow(k, -1, N) # Python 3.8+
s = ((z + r*e) * k_inv) % N
return Signature(r, s)

Apart from the fact that e is a secret number, the security of ECDSA also relies on the condition that k is also very random and secret.

We'll learn about the consequences of not having a random kin the next section.

Verification algorithm

  • Given: (r, s) is the signature, z is the 256 bit message being signed, and P is the public key of the signer.

  • Calculate: u=z/su = z/s, v=r/sv=r/s.

  • Calculate uG+vP=RuG + vP = R.

  • Signature is valid is RxRx is equal to rr.

class Signature:
r: int
s: int
def verify(self, z: int, pub_key: Point) -> bool:
s_inv = pow(self.s, -1, N) # Python 3.8+
u = (z * s_inv) % N
v = (self.r * s_inv) % N
return (u*G + v*pub_key).x.value == self.r

Testing our ECDSA implementation

pub = Point(
# Test case 1: verify authenticity
z = 0xEC208BAA0FC1C19F708A9CA96FDEFF3AC3F230BB4A7BA4AEDE4942AD003C0F60
r = 0xAC8D1C87E51D0D441BE8B3DD5B05C8795B48875DFFE00B7FFCFAC23010D3A395
s = 0x68342CEFF8935EDEDD102DD876FFD6BA72D6A427A3EDB13D26EB0781CB423C4
assert Signature(r, s).verify(z, pub)
# Test case 2: verify authenticity for different signature w/ same P
z = 0x7C076FF316692A3D7EB3C3BB0F8B1488CF72E1AFCD929E29307032997A838A3D
r = 0xEFF69EF2B1BD93A66ED5219ADD4FB51E11A840F404876325A1E8FFE0529A2C
s = 0xC7207FEE197D27C618AEA621406F6BF5EF6FCA38681D82B2F06FDDBDCE6FEAB6
assert Signature(r, s).verify(z, pub)
# Test case 3: sign and verify
e = PrivateKey(randint(0, N)) # generate a private key
pub = e.secret * G # public point corresponding to e
z = randint(0, 2 ** 256) # generate a random message for testing
signature: Signature = e.sign(z)
assert signature.verify(z, pub)