|
| 1 | + |
| 2 | +using TinyCrypto |
| 3 | + |
| 4 | +# ───────────────────────────────────────────────────────────────────────────── |
| 5 | +# ECDSA Signature and Public Key Recovery Example |
| 6 | +# ───────────────────────────────────────────────────────────────────────────── |
| 7 | + |
| 8 | +priv, pub = genkey(curve) # Key generation |
| 9 | +msg = "hello ethereum" # Message to sign |
| 10 | +signature = sign(curve, priv, msg) # returns a NamedTuple (r = ..., s = ..., v = ...) |
| 11 | +is_valid = verify(curve, pub, signature, msg) # Signature verification true |
| 12 | +recovered = ecrecover(curve, msg, signature) # Public key recovery from signature |
| 13 | +@assert pub == recovered # Check if recovered public key matches originald |
| 14 | + |
| 15 | +# ───────────────────────────────────────────────────────────────────────────── |
| 16 | +# The following steps (0–7) manually walk through the ECDSA signing and verification |
| 17 | +# process to illustrate what happens under the hood. The goal is to help understand |
| 18 | +# the mathematical structure behind the cryptographic operations shown above. |
| 19 | +# ───────────────────────────────────────────────────────────────────────────── |
| 20 | + |
| 21 | +# 1. Define a toy Weierstrass curve over 𝔽₃₁ with G of order 37 |
| 22 | +curve = Weierstrass(31, 6, 9, 37, 1, (0, 3)) |
| 23 | +# Weierstrass{𝔽₃₁}: y² = x³ + 6x + 9 | G = (0,3), order = 37, cofactor = 1 |
| 24 | + |
| 25 | +# 2. Create a mock blockchain transaction |
| 26 | +tx = "hello ethereum" |
| 27 | + |
| 28 | +# 3. Hash it (you fake Keccak256 with H₈ → SHA256 first 8 bytes) |
| 29 | +z = Int(H₈(tx)) % curve.order # H₈ returns UInt8; convert to Int for modular arithmetic |
| 30 | + |
| 31 | +# 4. Generate keypair |
| 32 | +priv = rand(1:curve.order - 1) # private key is a uniformly random integer in [1, curve order − 1] |
| 33 | +pub = priv * curve.G # public key is the scalar multiple of the generator: pub = priv · G |
| 34 | + |
| 35 | +r,s,v = 0,0,0 |
| 36 | +# 5. Sign: create ephemeral scalar `k` and calculate signature (r, s) |
| 37 | +while true |
| 38 | + k = rand(1:curve.order - 1) # draw a random ephemeral scalar used once per signature |
| 39 | + R = k * curve.G # then construct an ephemeral public point R = k·G on the curve |
| 40 | + v = isodd(R.point.y.val) ? 1 : 0 # and compute the parity (Ethereum) bit from LSB of y which is used for ECrecover |
| 41 | + |
| 42 | + r = mod(R.point.x, curve.order) # use x-coordinate of R as first signature component |
| 43 | + k_inv = invmod(k, curve.order) # compute modular inverse of ephemeral scalar |
| 44 | + s = mod(k_inv * (z + r * priv), curve.order) # second signature component s = k⁻¹(z + r·priv) mod n |
| 45 | + |
| 46 | + if r ≠ 0 && s ≠ 0 break end # loop until suitable `k` is found |
| 47 | +end |
| 48 | + |
| 49 | +# 6. The signature is (r, s, v + 27) `27` roots back to early Bitcoin compact signatures, parity bit is used on Ethereum |
| 50 | +# whereas Bitcoin uses recovery ID : = {0,1,2,3} |
| 51 | +println("Signature: (r = $r, s = $s, v = $(v + 27))") |
| 52 | + |
| 53 | +# 7. Verification |
| 54 | +s_inv = invmod(s, curve.order) # compute inverse of signature scalar s⁻¹ mod n |
| 55 | +u₁ = z * s_inv % curve.order # u₁ = z · s⁻¹ mod n |
| 56 | +u₂ = r * s_inv % curve.order # u₂ = r · s⁻¹ mod n |
| 57 | +P = u₁ * curve.G + u₂ * pub # recover point P = u₁·G + u₂·Q (should equal R) |
| 58 | + |
| 59 | +is_valid = mod(P.point.x, curve.order) == r # check if recovered R.x matches r component |
| 60 | +println("Signature valid? ", is_valid) |
0 commit comments