Skip to content

Conversation

ansiwen
Copy link

@ansiwen ansiwen commented Aug 1, 2025

This is a generic ECDSA implementation, that allows to register only the specific ECDSA implementation packages, that are actually required. This allows to remove unused curve implementations from the code base, but also to extend X509 with unusual curves, that are usually not available.

@ansiwen
Copy link
Author

ansiwen commented Aug 1, 2025

This is just PoC for now, because I would like to get some feedback if this in general would be something, that is interesting for the upstream project, because it probably can't be made backwards compatible. At least not, if removing the standard curves should also be supported. Also I am not sure about the performance impact by using a first-class module. My expectation would be, that it's negligible, but who knows.

@hannesm
Copy link
Member

hannesm commented Aug 1, 2025

Thanks. I'm on vacation at the moment. An immediate question would be:

Also I am not sure about the performance impact by using a first-class module. My expectation would be, that it's negligible, but who knows.

Would you mind to run a benchmark? Regarding this PR, it would make a big difference, if it is 10x slower or not.

let brainpoolP384r1 = brainpool_base <| 11
let brainpoolP384t1 = brainpool_base <| 12
let brainpoolP512r1 = brainpool_base <| 13
let brainpoolP512t1 = brainpool_base <| 14
Copy link
Member

Choose a reason for hiding this comment

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

these additions look unrelated to the scope of this PR.

Copy link
Author

Choose a reason for hiding this comment

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

I actually exported them in the Dsa_curves.OIDs module as a convenience for registering the curve implementations.

Copy link
Member

Choose a reason for hiding this comment

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

It's fine to export them, but I'd still prefer to have them added in a separate PR.

@ansiwen
Copy link
Author

ansiwen commented Aug 2, 2025

Would you mind to run a benchmark? Regarding this PR, it would make a big difference, if it is 10x slower or not.

Sure. Based on the speed.ml from mirage-crypto I created a benchmark for both the old and the generic interface. Fortunately, the results are indistinguishable:

Old:

* [ecdsa-generate]
    P256:  40400.613 ops per second (413223 iters in 10.228)
    P384:  14870.256 ops per second (150602 iters in 10.128)
    P521:  6756.630 ops per second (52910 iters in 7.831)

* [ecdsa-sign]
    P256:  23348.900 ops per second (242718 iters in 10.395)
    P384:  9934.042 ops per second (102249 iters in 10.293)
    P521:  7067.312 ops per second (67934 iters in 9.612)

* [ecdsa-verify]
    P256:  7508.620 ops per second (75187 iters in 10.013)
    P384:  2843.444 ops per second (29744 iters in 10.461)
    P521:  2213.566 ops per second (22946 iters in 10.366)
* [ecdsa-generate]
    P256:  39405.433 ops per second (420168 iters in 10.663)
    P384:  14906.406 ops per second (143678 iters in 9.639)
    P521:  6586.993 ops per second (65019 iters in 9.871)

* [ecdsa-sign]
    P256:  23606.283 ops per second (234741 iters in 9.944)
    P384:  9899.214 ops per second (96339 iters in 9.732)
    P521:  7023.184 ops per second (65274 iters in 9.294)

* [ecdsa-verify]
    P256:  7332.298 ops per second (77279 iters in 10.540)
    P384:  2856.388 ops per second (28425 iters in 9.951)
    P521:  2172.591 ops per second (21881 iters in 10.071)

new generic interface:

* [ecdsa-generate]
    P256:  40798.945 ops per second (416666 iters in 10.213)
    P384:  15225.544 ops per second (153374 iters in 10.073)
    P521:  6953.087 ops per second (63856 iters in 9.184)

* [ecdsa-sign]
    P256:  24161.835 ops per second (242718 iters in 10.046)
    P384:  10342.692 ops per second (106382 iters in 10.286)
    P521:  7355.721 ops per second (74404 iters in 10.115)

* [ecdsa-verify]
    P256:  7488.605 ops per second (77399 iters in 10.336)
    P384:  2841.765 ops per second (28818 iters in 10.141)
    P521:  2276.173 ops per second (22331 iters in 9.811)
* [ecdsa-generate]
    P256:  40268.478 ops per second (416666 iters in 10.347)
    P384:  14729.690 ops per second (154320 iters in 10.477)
    P521:  6833.052 ops per second (42016 iters in 6.149)

* [ecdsa-sign]
    P256:  23765.026 ops per second (251256 iters in 10.573)
    P384:  10139.299 ops per second (103519 iters in 10.210)
    P521:  7052.804 ops per second (74404 iters in 10.550)

* [ecdsa-verify]
    P256:  7490.177 ops per second (77639 iters in 10.365)
    P384:  2888.996 ops per second (27932 iters in 9.668)
    P521:  2261.728 ops per second (23073 iters in 10.201)

@hannesm
Copy link
Member

hannesm commented Aug 15, 2025

Thanks for the benchmark. Would this approach scale to not only ECDSA, but as well other PK algorithms (Ed25519, RSA)? I suspect we'd need a generic module signature for all these, but that may be worth doing (to provide an X509 package which doesn't depend on libgmp...). I'll think a bit more about it when I have time, but this PR is for sure in the right direction.

let curves, add_curve =
let curves = ref [] in
(fun () -> !curves),
(fun c -> curves := c :: !curves)
Copy link
Member

Choose a reason for hiding this comment

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

this uses a list, which is ever growing. I wonder what about registration of the same curve multiple times? Since the lookup in the list is done by name, shouldn't we use a map?

val pp : t Fmt.t
end
end

Copy link
Member

Choose a reason for hiding this comment

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

there's documentation lacking. For Dsa_curves purpose, and how to use it.

module type Dsa = Mirage_crypto_ec.Dsa
module type S = Dsa_curves.S

type t = (module S)
Copy link
Member

Choose a reason for hiding this comment

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

are module type S and type t needed to be exposed?

end

val register :
string -> Asn.oid -> (module Dsa) -> t
Copy link
Member

Choose a reason for hiding this comment

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

why return a t from register? shouldn't it just be a unit? or what is it that you like to do with t? (also, taking the other comment into account, should it possible to return an error to avoid multiple registrations of the same curve?)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants