Skip to content

Commit

Permalink
condensing ECDSA into DSA with CryptoGroups group polymorphism
Browse files Browse the repository at this point in the history
  • Loading branch information
Janis Erdmanis committed Sep 10, 2024
1 parent 2121696 commit a96a4a1
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 157 deletions.
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "CryptoSignatures"
uuid = "35cc5888-0c46-470e-89c7-eafcaf79a1aa"
authors = ["Janis Erdmanis <[email protected]>"]
version = "0.3.3"
version = "0.4.0"

[deps]
CryptoGroups = "bc997328-bedd-407e-bcd3-5758e064a52d"
Expand All @@ -11,7 +11,7 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[compat]
CryptoGroups = "0.5"
CryptoPRG = "0.1.1"
CryptoPRG = "0.1"
Nettle = "1"
julia = "1"

Expand Down
14 changes: 5 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ The first step is to select a curve to make a cryptographic signature with an el
using CryptoSignatures
import CryptoGroups

curve = CryptoGroups.curve("secp192r1")
ctx = ECDSAContext(curve, "sha1")
curve = CryptoGroups.spec(:secp192r1)
ctx = DSAContext(curve, "sha1")
```

where `ctx` stores all relevant parameters on how to make and verify signatures. The second argument specifies a hash function name, which is forwarded to `Nettle`. In case hashing is done externally to avoid hashing twice, nothing can be passed as an argument like `ECDSAContext(Curve_P_192, nothing)`.
where `ctx` stores all relevant parameters on how to make and verify signatures. The second argument specifies a hash function name, which is forwarded to `Nettle`. In case hashing is done externally to avoid hashing twice, nothing can be passed as an argument like `DSAContext(Curve_P_192, nothing)`.

To make a signature, first, we need to pick a key and calculate a corresponding public key:

Expand Down Expand Up @@ -49,13 +49,9 @@ To use an ordinary DSA with modular arithmetics, we need to instantiate the `DSA

```julia
using CryptoSignatures
import CryptoGroups.Specs: generate_pq, generate_g, MODP

p, q = generate_qp(100) # group order with 100 bits as an example (use > 2000)!
g = generate_g(p, q)

group = MODP(; p, q, g)
import CryptoGroups

group = CryptoGroups.spec(:RFC5114_2048_224)
ctx = DSAContext(group, "sha1")
```

Expand Down
206 changes: 65 additions & 141 deletions src/CryptoSignatures.jl
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module CryptoSignatures

using CryptoGroups: CryptoGroups, generator, concretize_type, octet, order, PGroup
using CryptoGroups.Curves: ECPoint, gx, gy
using CryptoGroups.Specs: MODP, ECP, EC2N, Koblitz, modulus
using CryptoGroups: CryptoGroups, generator, concretize_type, octet, order, PGroup, ECGroup, Group
#using CryptoGroups.Curves: ECPoint, gx, gy
using CryptoGroups.Specs: MODP, ECP, EC2N, Koblitz, GroupSpec
using CryptoGroups.Utils: octet2int, int2octet, @check

using CryptoPRG: bitlength
Expand Down Expand Up @@ -37,223 +37,147 @@ function generate_key(order::Integer)
end
end

function generate_k(order::Integer, key::BigInt, message::Vector{UInt8}, counter::UInt8 = 0x00)
#function generate_k(order::Integer, key::BigInt, message::Vector{UInt8}, counter::UInt8 = 0x00)
function generate_k(order::Integer, key::BigInt, e::BigInt, counter::UInt8 = 0x00)

n = bitlength(order)

key_bytes = int2octet(key, n)
e_bytes = int2octet(e, n)

prg = PRG("sha256"; s = UInt8[SEED..., key_bytes..., message..., counter])
prg = PRG("sha256"; s = UInt8[SEED..., key_bytes..., e_bytes..., counter])
k = rand(prg, BigInt; n) % order

if k == 0 || k == 1
return generate_k(order, key, message, counter + 0x01)
return generate_k(order, key, e, counter + 0x01)
else
return k
end
end


struct DSA
r::BigInt
s::BigInt
end

Base.:(==)(x::DSA, y::DSA) = x.r == y.r && x.s == y.s

struct ECDSAContext
curve::Union{ECP, EC2N, Koblitz}
hasher::Union{String, Nothing}
end

CryptoGroups.generator(ctx::ECDSAContext) = generator(ctx.curve)

generate_key(ctx::ECDSAContext) = generate_key(order(ctx.curve))

H(message::Vector{UInt8}, hasher) = octet2int(hex2bytes(hexdigest(hasher, message)))
H(message::Vector{UInt8}, ::Nothing) = octet2int(message) # for situations where hash is computed externally


function generator_octet(spec::Union{ECP, EC2N, Koblitz})

P = concretize_type(ECPoint, spec)
point = P(generator(spec))

return octet(point)
end

generator_octet(ctx::ECDSAContext) = generator_octet(ctx.curve)


function sign(ctx::ECDSAContext, message::Vector{UInt8}, generator::Vector{UInt8}, key::BigInt; counter::UInt8 = 0x00, k::BigInt = generate_k(order(ctx.curve), key, message, counter))

P = concretize_type(ECPoint, ctx.curve) # additional parameters could be passed here if needed for different backends
G = P(generator) # in this setting P can also be soft typed

e = H(message, ctx.hasher)

R = k*G
function sign(e::BigInt, g::G, key::BigInt; counter::UInt8 = 0x00, k::BigInt = generate_k(order(G), key, e, counter)) where G <: Group

= gx(R)
q = order(G)

n = order(P)
r =% n
r = g^k % q

s = invmod(k, n) * (e + key * r) % n
s = invmod(k, q) * (e + key * r) % q

if 1 < r < n - 1 && 1 < s < n - 1
if 1 < r < q - 1 && 1 < s < q - 1
return DSA(r, s)
else
return sign(ctx, message, generator, key; counter = counter + 0x01)
return sign(e, g, key; counter = counter + 0x01)
end
end


sign(ctx::ECDSAContext, message::Vector{UInt8}, key::BigInt; kwargs...) = sign(ctx, message, generator_octet(ctx), key; kwargs...)


function verify(ctx::ECDSAContext, message::Vector{UInt8}, generator::Vector{UInt8}, pbkey::Vector{UInt8}, signature::DSA)
function verify(e::BigInt, g::G, y::G, signature::DSA) where G <: Group

(; r, s) = signature

P = concretize_type(ECPoint, ctx.curve)
G = P(generator)
Q = P(pbkey)

e = H(message, ctx.hasher)
n = order(P)

@check 1 < r < n - 1
@check 1 < s < n - 1
q = order(G)

c = invmod(s, n)
@check 1 < r < q - 1
@check 1 < s < q - 1

u₁ = e*c % n
u₂ = r*c % n
w = invmod(s, q)

u1 = e * w % q
u2 = r * w % q

if u₁ == 0
W = u₂*Q
elseif u₂ == 0
W = u₁*G
# # Raising group element to 0 not allowed. Perhaps need to change that.
if u1 == 0
v = y^u2 % q
elseif u2 == 0
v = g^u1 % q
else
W = u₁*G + u₂*Q
v = g^u1 * y^u2 % q
end

= gx(W)
ν =% n # I could also rewrite it as ν = W % n

return ν == r
return v == r
end


verify(ctx::ECDSAContext, message::Vector{UInt8}, pbkey::Vector{UInt8}, signature::DSA) = verify(ctx, message, generator_octet(ctx), pbkey, signature)



function public_key(ctx::ECDSAContext, generator::Vector{UInt8}, private_key::BigInt; mode=:compressed)

P = concretize_type(ECPoint, ctx.curve)
G = P(generator)

Q = private_key * G

return octet(Q; mode)
struct DSAContext
group::GroupSpec
hasher::Union{String, Nothing}
end

public_key(ctx::ECDSAContext, private_key::BigInt; mode=:compressed) = public_key(ctx, generator_octet(ctx), private_key; mode)
CryptoGroups.generator(ctx::DSAContext) = generator(ctx.group)

generate_key(ctx::DSAContext) = generate_key(order(ctx.group))

struct DSAContext
group::MODP
hasher::Union{String, Nothing}
end
H(message::Vector{UInt8}, hasher) = octet2int(hex2bytes(hexdigest(hasher, message)))
H(message::Vector{UInt8}, ::Nothing) = octet2int(message) # for situations where hash is computed externally


CryptoGroups.generator(ctx::DSAContext) = generator(ctx.group)
function generator_octet(spec::GroupSpec)

G = initialize_spec_type(spec)
g = G(generator(spec))

function generator_octet(spec::MODP)
g = generator(spec)
return int2octet(g, bitlength(modulus(spec)))
return octet(g)
end

generator_octet(ctx::DSAContext) = generator_octet(ctx.group)

initialize_spec_type(curve::Union{ECP, EC2N, Koblitz}) = concretize_type(ECGroup, curve)
initialize_spec_type(modp::MODP) = concretize_type(PGroup, modp)

function sign(ctx::DSAContext, message::Vector{UInt8}, generator::Vector{UInt8}, key::BigInt; counter::UInt8 = 0x00, k::BigInt = generate_k(order(ctx.group), key, message, counter))
function sign(ctx::DSAContext, message::Vector{UInt8}, generator::Vector{UInt8}, key::BigInt; k = nothing)

G = concretize_type(PGroup, ctx.group)
g = G(generator)
G = initialize_spec_type(ctx.group) # additional parameters could be passed here if needed for different backends
g = G(generator) # in this setting P can also be soft typed

e = H(message, ctx.hasher)
q = order(G)

r = g^k % q

s = invmod(k, q) * (e + key * r) % q

if 1 < r < q - 1 && 1 < s < q - 1
return DSA(r, s)
# Is there a more idiomatic way to do this?
if isnothing(k)
return sign(e, g, key)
else
return sign(ctx, message, generator, key; counter = counter + 0x01)
return sign(e, g, key; k)
end
end


sign(ctx::DSAContext, message::Vector{UInt8}, key::BigInt; kwargs...) = sign(ctx, message, generator_octet(ctx), key; kwargs...)

sign(ctx::DSAContext, message::Vector{UInt8}, key::BigInt; k = nothing) = sign(ctx, message, generator_octet(ctx), key; k)

function verify(ctx::DSAContext, message::Vector{UInt8}, generator::Vector{UInt8}, pbkey::Vector{UInt8}, signature::DSA)

(; r, s) = signature
G = concretize_type(PGroup, ctx.group)
G = initialize_spec_type(ctx.group)

g = G(generator)
g = G(generator)
y = G(pbkey)

e = H(message, ctx.hasher)

e = H(message, ctx.hasher)
q = order(G)

@check 1 < r < q - 1
@check 1 < s < q - 1

w = invmod(s, q)

u1 = e * w % q
u2 = r * w % q

# # Raising group element to 0 not allowed. Perhaps need to change that.
if u1 == 0
v = y^u2 % q
elseif u2 == 0
v = g^u1 % q
else
v = g^u1 * y^u2 % q
end

return v == r
return verify(e, g, y, signature)
end

verify(ctx::DSAContext, message::Vector{UInt8}, pbkey::Vector{UInt8}, signature::DSA) = verify(ctx, message, generator_octet(ctx), pbkey, signature)



function public_key(ctx::DSAContext, generator::Vector{UInt8}, private_key::BigInt)

G = concretize_type(PGroup, ctx.group)

function public_key(ctx::DSAContext, generator::Vector{UInt8}, private_key::BigInt; mode=:compressed)

G = initialize_spec_type(ctx.group)
g = G(generator)

Q = g^private_key
y = g^private_key

return octet(Q)
if ctx.group isa MODP
return octet(y)
else
return octet(y; mode)
end
end

public_key(ctx::DSAContext, private_key::BigInt) = public_key(ctx, generator_octet(ctx), private_key)
public_key(ctx::DSAContext, private_key::BigInt; mode=:compressed) = public_key(ctx, generator_octet(ctx), private_key; mode)

generate_key(ctx::DSAContext) = generate_key(order(ctx.group))

export sign, verify, DSA, ECDSAContext, public_key, DSAContext
export sign, verify, generate_key, public_key, DSA, DSAContext

end # module
2 changes: 1 addition & 1 deletion test/degenracy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ end


curve = ECP(; p = 23, a = 1, b = 4, n = 29, cofactor = 1, Gx = 0, Gy = 2)
ctx = ECDSAContext(curve, "sha256")
ctx = DSAContext(curve, "sha256")

for i in 0:255

Expand Down
2 changes: 1 addition & 1 deletion test/ec2n.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ curve = EC2N(basis;
cofactor = 2
)

ctx = ECDSAContext(curve, "sha1")
ctx = DSAContext(curve, "sha1")


d = 1275552191113212300012030439187146164646146646466749494799
Expand Down
4 changes: 2 additions & 2 deletions test/ecdsa_example.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ using Test
using CryptoSignatures
import CryptoGroups

curve = CryptoGroups.curve("secp192r1")
ctx = ECDSAContext(curve, "sha1")
curve = CryptoGroups.spec(:secp192r1)
ctx = DSAContext(curve, "sha1")

private_key = CryptoSignatures.generate_key(ctx)
public_key = CryptoSignatures.public_key(ctx, private_key; mode = :uncompressed)
Expand Down
2 changes: 1 addition & 1 deletion test/ecp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ curve = ECP(;
G = hex"03 188DA80E B03090F6 7CBF20EB 43A18800 F4FF0AFD 82FF1012",
)

ctx = ECDSAContext(curve, "sha1")
ctx = DSAContext(curve, "sha1")


d = 651056770906015076056810763456358567190100156695615665659
Expand Down

2 comments on commit a96a4a1

@JanisErdmanis
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/114878

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.4.0 -m "<description of version>" a96a4a1010382e9912d034cb78a146307fa44669
git push origin v0.4.0

Please sign in to comment.