261 lines
5.9 KiB
Nim
261 lines
5.9 KiB
Nim
import math, random, strformat
|
||
|
||
const
|
||
MaxN = 1073741789 # Maximum modulus.
|
||
MaxR = MaxN + 65536 # Maximum order "g".
|
||
Infinity = int64.high # Symbolic infinity.
|
||
|
||
type
|
||
|
||
Point = tuple[x, y: int64]
|
||
|
||
Curve = object
|
||
a, b: int64
|
||
n: int64
|
||
g: Point
|
||
r: int64
|
||
|
||
Pair = tuple[a, b: int64]
|
||
|
||
Parameters = tuple[a, b, n, gx, gy, r: int]
|
||
|
||
const ZerO: Point = (Infinity, 0i64)
|
||
|
||
type
|
||
InversionError = object of ValueError
|
||
InvalidParamError = object of ValueError
|
||
|
||
|
||
template `%`(a, n: int64): int64 =
|
||
## To simplify the writing.
|
||
floorMod(a, n)
|
||
|
||
|
||
proc exgcd(v, u: int64): int64 =
|
||
## Return 1/v mod u.
|
||
var u = u
|
||
var v = v
|
||
if v < 0: v += u
|
||
|
||
var r = 0i64
|
||
var s = 1i64
|
||
while v != 0:
|
||
let q = u div v
|
||
u = u mod v
|
||
swap u, v
|
||
r -= q * s
|
||
swap r, s
|
||
|
||
if u != 1:
|
||
raise newException(InversionError, "impossible inverse mod N, gcd = " & $u)
|
||
result = r
|
||
|
||
|
||
func discr(e: Curve): int64 =
|
||
## Return the discriminant of "e".
|
||
let c = e.a * e.a % e.n * e.a % e.n * 4
|
||
result = -16 * (e.b * e.b % e.n * 27 + c) % e.n
|
||
|
||
|
||
func isO(p: Point): bool =
|
||
## Return true if "p" is zero.
|
||
p.x == Infinity and p.y == 0
|
||
|
||
|
||
func isOn(e: Curve; p: Point): bool =
|
||
## Return true if "p" is on curve "e".
|
||
if p.isO: return true
|
||
let r = ((e.a + p.x * p.x) % e.n * p.x + e.b) % e.n
|
||
let s = p.y * p.y % e.n
|
||
result = r == s
|
||
|
||
|
||
proc add(e: Curve; p, q: Point): Point =
|
||
## Full Point addition.
|
||
|
||
if p.isO: return q
|
||
if q.isO: return p
|
||
|
||
var la: int64
|
||
if p.x != q.x:
|
||
la = (p.y - q.y) * exgcd(p.x - q.x, e.n) % e.n
|
||
elif p.y == q.y and p.y != 0:
|
||
la = (p.x * p.x % e.n * 3 + e.a) % e.n * exgcd(2 * p.y, e.n) % e.n
|
||
else:
|
||
return ZerO
|
||
|
||
result.x = (la * la - p.x - q.x) % e.n
|
||
result.y = (la * (p.x - result.x) - p.y) % e.n
|
||
|
||
|
||
proc mul(e: Curve; p: Point; k: int64): Point =
|
||
## Return "kp".
|
||
var q = p
|
||
var k = k
|
||
result = ZerO
|
||
|
||
while k != 0:
|
||
if (k and 1) != 0:
|
||
result = e.add(result, q)
|
||
q = e.add(q, q)
|
||
k = k shr 1
|
||
|
||
|
||
proc print(e: Curve; prefix: string; p: Point) =
|
||
## Print a point with a prefix.
|
||
var y = p.y
|
||
if p.isO:
|
||
echo prefix, " (0)"
|
||
else:
|
||
if y > e.n - y: y -= e.n
|
||
echo prefix, &" ({p.x}, {y})"
|
||
|
||
|
||
proc initCurve(params: Parameters): Curve =
|
||
## Initialize the curve.
|
||
|
||
result.n = params.n
|
||
if result.n notin 5..MaxN:
|
||
raise newException(ValueError, "invalid value for N: " & $result.n)
|
||
result.a = params.a.int64 % result.n
|
||
result.b = params.b.int64 % result.n
|
||
result.g.x = params.gx.int64 % result.n
|
||
result.g.y = params.gy.int64 % result.n
|
||
result.r = params.r
|
||
|
||
if result.r notin 5..MaxR:
|
||
raise newException(ValueError, "invalid value for r: " & $result.r)
|
||
|
||
echo &"\nE: y^2 = x^3 + {result.a}x + {result.b} (mod {result.n})"
|
||
result.print("base point G", result.g)
|
||
echo &"order(G, E) = {result.r}"
|
||
|
||
|
||
proc rnd(): float =
|
||
## Return a pseudorandom number in range [0..1[.
|
||
while true:
|
||
result = rand(1.0)
|
||
if result != 1: break
|
||
|
||
proc signature(e: Curve; s, f: int64): Pair =
|
||
## Compute the signature.
|
||
|
||
var
|
||
c, d = 0i64
|
||
u: int64
|
||
v: Point
|
||
|
||
echo "Signature computation"
|
||
|
||
while true:
|
||
while true:
|
||
u = 1 + int64(rnd() * float(e.r - 1))
|
||
v = e.mul(e.g, u)
|
||
c = v.x % e.r
|
||
if c != 0: break
|
||
d = exgcd(u, e.r) * (f + s * c % e.r) % e.r
|
||
if d != 0: break
|
||
|
||
echo "one-time u = ", u
|
||
e.print("V = uG", v)
|
||
|
||
result = (c, d)
|
||
|
||
|
||
proc verify(e: Curve; w: Point; f: int64; sg: Pair): bool =
|
||
## Verify a signature.
|
||
|
||
# Domain check.
|
||
if sg.a notin 1..<e.r or sg.b notin 1..<e.r:
|
||
return false
|
||
|
||
echo "\nsignature verification"
|
||
let h = exgcd(sg.b, e.r)
|
||
let h1 = f * h % e.r
|
||
let h2 = sg.a * h % e.r
|
||
echo &"h1, h2 = {h1}, {h2}"
|
||
var v = e.mul(e.g, h1)
|
||
let v2 = e.mul(w, h2)
|
||
e.print("h1G", v)
|
||
e.print("h2W", v2)
|
||
v = e.add(v, v2)
|
||
e.print("+ =", v)
|
||
if v.isO: return false
|
||
let c1 = v.x % e.r
|
||
echo "c’ = ", c1
|
||
result = c1 == sg.a
|
||
|
||
|
||
proc ecdsa(e: Curve; f: int64; d: int) =
|
||
## Build digital signature for message hash "f" with error bit "d".
|
||
|
||
# Parameter check.
|
||
var w = e.mul(e.g, e.r)
|
||
if e.discr() == 0 or e.g.isO or not w.isO or not e.isOn(e.g):
|
||
raise newException(InvalidParamError, "invalid parameter set")
|
||
|
||
echo "\nkey generation"
|
||
let s = 1 + int64(rnd() * float(e.r - 1))
|
||
w = e.mul(e.g, s)
|
||
echo "private key s = ", s
|
||
e.print("public key W = sG", w)
|
||
|
||
# Find next highest power of two minus one.
|
||
var t = e.r
|
||
var i = 1
|
||
while i < 64:
|
||
t = t or t shr i
|
||
i = i shl 1
|
||
var f = f
|
||
while f > t: f = f shr 1
|
||
echo &"\naligned hash {f:x}"
|
||
|
||
let sg = e.signature(s, f)
|
||
echo &"signature c, d = {sg.a}, {sg.b}"
|
||
|
||
var d = d
|
||
if d > 0:
|
||
while d > t: d = d shr 1
|
||
f = f xor d
|
||
echo &"\ncorrupted hash {f:x}"
|
||
|
||
echo if e.verify(w, f, sg): "Valid" else: "Invalid"
|
||
|
||
when isMainModule:
|
||
|
||
randomize()
|
||
|
||
# Test vectors: elliptic curve domain parameters,
|
||
# short Weierstrass model y^2 = x^3 + ax + b (mod N)
|
||
|
||
const Sets = [
|
||
# a, b, modulus N, base point G, order(G, E), cofactor
|
||
(355, 671, 1073741789, 13693, 10088, 1073807281),
|
||
( 0, 7, 67096021, 6580, 779, 16769911),
|
||
( -3, 1, 877073, 0, 1, 878159),
|
||
( 0, 14, 22651, 63, 30, 151),
|
||
( 3, 2, 5, 2, 1, 5),
|
||
|
||
# ECDSA may fail if...
|
||
# the base point is of composite order
|
||
( 0, 7, 67096021, 2402, 6067, 33539822),
|
||
# the given order is of composite order
|
||
( 0, 7, 67096021, 6580, 779, 67079644),
|
||
# the modulus is not prime (deceptive example)
|
||
( 0, 7, 877069, 3, 97123, 877069),
|
||
# fails if the modulus divides the discriminant
|
||
( 39, 387, 22651, 95, 27, 22651)
|
||
]
|
||
|
||
# Digital signature on message hash f,
|
||
# set d > 0 to simulate corrupted data.
|
||
let f = 0x789abcdei64
|
||
let d = 0
|
||
|
||
for s in Sets:
|
||
let e = initCurve(s)
|
||
try:
|
||
e.ecdsa(f, d)
|
||
except ValueError:
|
||
echo getCurrentExceptionMsg()
|
||
echo "——————————————"
|