186 lines
5.6 KiB
Julia
186 lines
5.6 KiB
Julia
module ToyECDSA
|
|
|
|
using SHA
|
|
|
|
import Base.in, Base.==, Base.+, Base.*
|
|
|
|
export ECDSA_Key, ECDSA_Public_Key, genkey, ECDSA_sign, isverifiedECDSA
|
|
|
|
# T will be BigInt in most applications
|
|
struct CurveFP{T}
|
|
p::T
|
|
a::T
|
|
b::T
|
|
CurveFP(p, a::T, b::T) where T <: Number = new{T}(p, a, b)
|
|
end
|
|
|
|
struct PointEC{T}
|
|
curve::CurveFP{T}
|
|
x::T
|
|
y::T
|
|
order::Union{Number, Nothing}
|
|
function PointEC(curve, x::T, y::T, order=nothing) where T <: Number
|
|
@assert((x, y) in curve)
|
|
new{T}(curve, x, y, order)
|
|
end
|
|
end
|
|
|
|
struct PointINF end
|
|
const INFINITY = PointINF()
|
|
|
|
function ==(point_a::PointEC, point_b::PointEC)
|
|
if point_a.curve == point_b.curve && point_a.x == point_b.x && point_a.y == point_b.y
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
function ==(curve_a::CurveFP, curve_b::CurveFP)
|
|
if curve_a.a == curve_b.a && curve_a.b == curve_b.b && curve_a.p == curve_b.p
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
+(point_a::PointINF, point_b::PointINF) = point_b
|
|
+(point_a::PointINF, point_b::PointEC) = point_b
|
|
+(point_a::PointEC, point_b::PointINF) = point_a
|
|
|
|
function +(point_a::PointEC, point_b::PointEC)
|
|
@assert(point_a.curve == point_b.curve)
|
|
if point_a.x == point_b.x
|
|
if (point_a.y + point_b.y) % point_a.curve.p == 0
|
|
return INFINITY
|
|
else
|
|
return double(point_a)
|
|
end
|
|
end
|
|
p = point_a.curve.p
|
|
λ = (point_a.y - point_b.y) * invmod(point_a.x - point_b.x, p)
|
|
xr = mod(λ * λ - point_a.x - point_b.x, p)
|
|
yr = mod(λ * (point_a.x - xr) - point_a.y, p)
|
|
return PointEC(point_a.curve, xr, yr, point_a.order)
|
|
end
|
|
|
|
*(point_a::PointINF, int_b::Number) = INFINITY
|
|
*(int_b::Number, point_a::PointINF) = INFINITY
|
|
*(int_b::Number, point_a::PointEC) = point_a * int_b
|
|
|
|
function *(point_a::PointEC, int_b::Number)
|
|
leftmost_bit(x) = big"2"^Int(trunc(log(2, x)))
|
|
if point_a.order != nothing
|
|
int_b %= point_a.order
|
|
end
|
|
if int_b == 0
|
|
return INFINITY
|
|
end
|
|
int_3b = 3 * int_b
|
|
negative_a = PointEC(point_a.curve, point_a.x, -point_a.y, point_a.order)
|
|
i = BigInt(leftmost_bit(int_3b) ÷ 2)
|
|
result = point_a
|
|
while i > 1
|
|
result = double(result)
|
|
if (int_3b & i) != 0 && (int_b & i) == 0
|
|
result += point_a
|
|
end
|
|
if (int_3b & i) == 0 && (int_b & i) != 0
|
|
result += negative_a
|
|
end
|
|
i ÷= 2
|
|
end
|
|
return result
|
|
end
|
|
|
|
in(z::Tuple, curve::CurveFP) = (z[2]^2 - (z[1]^3 + curve.a*z[1] + curve.b)) % curve.p == 0
|
|
in(x::Number,y::Number, curve::CurveFP) = (y^2 -(x^3 + curve.a*x + curve.b)) % curve.p == 0
|
|
in(p::PointEC, curve::CurveFP) = (p.y^2 - (p.x^3 + curve.a * p.x + curve.b)) % curve.p == 0
|
|
in(point::PointINF, curve::CurveFP) = true
|
|
|
|
double(point_a::PointINF) = INFINITY
|
|
|
|
function double(point_a::PointEC)
|
|
a, p = point_a.curve.a, point_a.curve.p
|
|
l = mod((3 * point_a.x^2 + a) * invmod(2 * point_a.y, p), p)
|
|
x3 = mod(l^2 - 2 * point_a.x, p)
|
|
y3 = mod(l * (point_a.x - x3) - point_a.y, p)
|
|
return PointEC(point_a.curve, x3, y3, point_a.order)
|
|
end
|
|
|
|
const secp256k1 = ( # use the Bitcoin ECDSA curve
|
|
p = big"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F",
|
|
a = big"0x0",
|
|
b = big"0x7",
|
|
r = big"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
|
|
Gx = big"0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
|
|
Gy = big"0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8",
|
|
)
|
|
@assert((secp256k1.Gy^2 - secp256k1.Gx^3 - 7) % secp256k1.p == 0)
|
|
|
|
function generatemultiplier(stdcurve)
|
|
return foldl((x, y) -> 16 * BigInt(x) + y, rand(0:16, ndigits(stdcurve.r - 1, base=16)))
|
|
end
|
|
|
|
struct ECDSA_Key
|
|
E::CurveFP
|
|
secret::BigInt
|
|
G::PointEC
|
|
r::BigInt
|
|
W::PointEC
|
|
end
|
|
|
|
struct ECDSA_Public_Key
|
|
E::CurveFP
|
|
G::PointEC
|
|
r::BigInt
|
|
W::PointEC
|
|
end
|
|
|
|
function genkey(curve=secp256k1) # default: use Bitcoin standard EC curve secp256k1
|
|
E = CurveFP(curve.p, curve.a, curve.b)
|
|
s = generatemultiplier(curve)
|
|
G = PointEC(E, curve.Gx, curve.Gy, curve.r)
|
|
W = s * G
|
|
return ECDSA_Key(E, s, G, curve.r, W)
|
|
end
|
|
|
|
aspublickey(k::ECDSA_Key) = ECDSA_Public_Key(k.E, k.G, k.r, k.W)
|
|
privatekey(k::ECDSA_Key) = k.secret
|
|
|
|
function ECDSA_sign(m::String, key::ECDSA_Key, digestfunction=sha256)
|
|
r, f = key.r, digestfunction(codeunits(m)) # f = H(m)
|
|
# order of curve points length must be >= sha digest length (in bytes)
|
|
@assert(ndigits(r, base=16) >= length(f))
|
|
c, d, bindigest = BigInt(0), BigInt(0), foldl((x, y) -> 16 * BigInt(x) + y, f)
|
|
while c == 0 || d == 0
|
|
u = generatemultiplier(secp256k1)
|
|
V = u * key.G
|
|
c = mod(V.x, r)
|
|
d = mod((invmod(u, r) * (bindigest + key.secret * c)), r)
|
|
end
|
|
return aspublickey(key), c, d
|
|
end
|
|
|
|
function isverifiedECDSA(m::String, publickey, c, d, digestfunction=sha256)
|
|
if 1 <= c < publickey.r && 1 <= d < publickey.r
|
|
r, f = publickey.r, digestfunction(codeunits(m))
|
|
h, bindigest = invmod(d, r), foldl((x, y) -> 16 * BigInt(x) + y, f)
|
|
h1, h2 = mod(bindigest * h, r), mod(c * h, r)
|
|
verifierpoint = h1 * publickey.G + h2 * publickey.W
|
|
return mod(verifierpoint.x, r) == c
|
|
end
|
|
return false
|
|
end
|
|
|
|
end # module
|
|
|
|
using .ToyECDSA
|
|
|
|
const key = genkey()
|
|
const msg = "Bill says this is an elliptic curve digital signature algorithm."
|
|
const altered = "Bill says this isn't an elliptic curve digital signature algorithm."
|
|
|
|
publickey, c, d = ECDSA_sign(msg, key)
|
|
|
|
println("ECDSA of message <$msg> verified: ", isverifiedECDSA(msg, publickey, c, d))
|
|
|
|
println("ECDSA of message <$altered> verified: ", isverifiedECDSA(altered, publickey, c, d))
|