x.crypto.mldsa: add ML-DSA signature algorithm#26711
x.crypto.mldsa: add ML-DSA signature algorithm#26711cestef wants to merge 29 commits intovlang:masterfrom
Conversation
…ess to relevant funcs
|
Tsk. The Markdown stuff is from @medvednikov changelog edits. Don't worry about those. |
|
Closing/re-opening to re-run CI with latest V. |
|
You have one example in the readme... how about some tests, to guard against regressions? |
|
|
||
| module sha3 | ||
|
|
||
| pub struct Shake { |
There was a problem hiding this comment.
Is this should be marked with @[noinit] tag ? there are constructor for this and i think the outside user should be prohibited to create this opaque directly with sha3.Shake{}
| // write absorbs more data into the sponge state. | ||
| // Panics if called after `read`. | ||
| @[direct_array_access] | ||
| pub fn (mut s Shake) write(data []u8) { |
There was a problem hiding this comment.
I think its should follow the io.Writer and hash.Hash.write semantics, so aligns with the rest
There was a problem hiding this comment.
the Shake XOF API intentionally differs from io.Writer/io.Reader (I initially implemented it like you mentioned), absorption never fails (so !int is kindof misleading) and squeezing produces exact byte counts (so read(n) []u8 makes more sense than preallocating a buffer imo?). Go's ShakeHash also never returns errors from write/read, the signatures exist only for interface conformance. No harm in implementing a io.Reader/Writer wrapper though, if that's you meant!
| // read squeezes `out_len` bytes from the sponge state. | ||
| // Finalizes the sponge on first call; further calls to `write` will panic. | ||
| @[direct_array_access] | ||
| pub fn (mut s Shake) read(out_len int) []u8 { |
There was a problem hiding this comment.
Even we have no interface XOF, i think this also should follow the io.Reader semantic
I was told they should be moved to vlang/slower_tests, see vlang/slower_tests#5 I will be adding a few basic test vectors back here as well, maybe validating against the go impl as @tankf33der mentioned |
@cestef - I remind you that they should have as much coverage as possible, whatever that means — only you can do this, it is your responsibility. Thank you. |
|
I added a we currently test against 12 test vecs (4 per kind, 44/65/87), to regenerate test vectors: v run vlib/x/crypto/mldsa/testdata/gen.vshthis will pull https://github.com/golang/go on master to is this what you had in mind? @tankf33der |
|
It doesn't need to be tested against the Go code, it just needs to be tested that it gets the "correct" output. In other words, hash something, then check that against the known hash value. Things like that. Simple unit tests that make sure the code isn't completely broken. Small tests like that can go in the main repo. Larger tests that take longer should go to the slow tests repo. |
@JalonSolov do you think this is too overkill? |
|
Not as long as they don't slow things down too much. And generating the hashes with Go is completely disconnected. Technically, Go shouldn't be involved at all, unless we find a problem with the hashes already generated - those should be hard-coded in the tests. |
|
CI having a rough day huh |
|
@cestef I played around with the code and got a lot of emotions out of it. I managed to make the code hang on the signature. If you make the private key fields public and modifiable and simply zero out the first value, the code will hang. This is not a problem, right? import x.crypto.mldsa
fn main() {
mut pr := mldsa.PrivateKey.generate(mldsa.Kind.ml_dsa_44)!
pr.t0[0][0] = 0
_ := pr.sign([]u8{len: 10_000, init: index})!
} |
|
@tankf33der yeah that's expected, ML-DSA signing uses rejection sampling. so if you corrupt the key internals it can loop forever. I'll add a max iteration count as a safeguard though, good catch |
|
@cestef - add tests from |
This adds the ML-DSA signature algorithm, as per FIPS 204. The code added was ported from golang's own implementation of the algorithm, thus why I added their BSD license to the code.
They are also in the process of discussing whether to add this impl to the public go
cryptoAPI: golang/go#77626ML-DSA is NIST's postquantum digital signature standard, which is believed to be resistant against quantum computers. It leverages the shortest vector problem as the one-way function, more specifically the short integer problem.
We implement the three parameters sets:
ML-DSA-44,ML-DSA-65andML-DSA-87(specified in FIPS 204, sec. 4).Even though optional, Pre-Hash ML-DSA is also implemented in
prehash.v(we basically sign the hash of the message instead of the message itself), and is disabled by default (configurable on.sign(...)viaprehash: <hash-alg>).The code lives at
vlib/x/crypto/mldsa, and XOF was added to thevlib/crypto/sha3module inxof.v. I can split this into a separate PR if you think this is too much.XOF was needed because ML-DSA makes extensive use of it: we already exposed
sha3.shakeXXX()oneshot functions, but here we need to stream input in multiple writes before reading output. See FIPS 202 if you're curious about it, I also have a short post with visual explainations of XOFs: https://blog.cstef.dev/posts/spongeCode is mainly tested by using NIST's ACVP test vectors (615 total):
nist_keygen_test.v(75 vecs)nist_sigver_test.v(180 vecs)nist_siggen_test.v(360 vecs)Example
Benchmarks (Intel Core Ultra 5 135U)
mldsa_bench_test.vvsBenchmarkMLDSA@crypto/internal/fips140testCloses #26683
This replaces #26708, which was accidentally closed during a history rewrite.