I am Karolin Varner, the person who designed the Rosenpass Protocol, which secures WireGuard against quantum attacks, and I am the managing director of Rosenpass e. V. I am well connected in the real-world cryptography scientific community and do cryptography daily.
Based on the responses from the author in this thread, I would strongly advise anyone against using this library for the following reasons:
- Lack of third-party reviews/existing review processes
- Somewhat evasive/defensive answers about LLM usage in this thread
- Lack of complete constant-time cryptography support
- The author's own insistence that this pre-v1 code (if it's pre-v1 and thus not yet at quality, don't advertise it for use. This is cryptography.) This claim is especially alarming since it's not found in the README, which does beg users to employ this library.
- Publishing this and then forcing everyone else to review this is extracting free labor from cryptographers. Code duplication in the code base extracts extra labor from cryptographers checking this. It's simply not good neighborly behavior.
The author claims themselves that the library is not fully constant-time secure; constant-time security is a basic guarantee all cryptography code MUST follow.
AUTHOR QUOTE:
NO global constant-time claim for:
- parsers/importers/DER/PHC decoding
- algo/profile negotiation
- keygen and OS randomness paths
- public RSA verification/encryption work
- hashes/checksums/fast hashes as whole APIs
- length/shape rejection before a primitive boundary
- Argon2d/scrypt as blanket CT primitives
Honestly, I am having a hard time making sense of this comment. A lot of these clearly should be constant time; not making them constant time is by definition insecure.
Given that this code is partially LLM-generated, I am dubious about whether every line of the code that is claimed to be constant-time secure is actually so.
It is also very confusing that the author claims constant-time security for MACs but not for (cryptographic) hashes. MACs (message authentication codes) are implemented in terms of hash functions, so how can the MAC be constant-time secure if the hash function is not? It makes no sense.
I suspect that the speedup reported might be in part due to the lack of constant-time security. Constant-time code comes with a performance penalty; if you are not doing constant time and the other people are, then of course your code will be faster.
It is particularly troubling that the author talks about "algo/profile negotiation"; this type of feature was a frequent source of dangerous vulnerabilities in SSL/TLS implementations (look up downgrade attacks). Also, for a library providing primitives, why is algo/profile negotiation even needed?
In short: If you want your projects to be secure, please do not use this. And please, dear author, do not publish half-baked crates in this way. It's disrespectful and steals our time as cryptographers.
Karolin, I’ll answer this directly, and try to stay on track, because several claims here are materially wrong.
First things first, I am not claiming I've had any third-party audit; I'm not claiming I've been FIPS validated, or telling anyone “use this blindly in high-assurance production.” If that is your bar, then yes: do not use rscrypto yet. That is a fair warning... and it's plastered everywhere. I almost feel like you didn't read the codebase, the thread here, etc.
Now, to your constant-time criticism - it's just not correct. It's wrong. It's misleading. It shouldn't come from someone leading Rosenpass.
“Not a global constant-time claim” does not mean “secret-bearing cryptographic ops branch on secrets.” I've said this twice now - verbatim. It means I am refusing to make a fake blanket claim over APIs where constant-time behavior is either irrelevant, impossible, or the wrong security property.
DER parsing is not supposed to be constant-time over DER structure. PHC string decoding is not supposed to be constant-time over ASCII syntax. OS randomness is not a constant-time primitive. RSA public verification/encryption operates on public inputs. Rejection of public length/shape before entering a primitive boundary is NOT a secret-dependent timing leak. Argon2d intentionally has data-dependent memory access; treating it as a blanket CT primitive would be wrong - it's a lie at that point.
That list was a boundary, Karolin, not an admission of insecure primitives.
The MAC/hash point is also mixing categories. A MAC verification claim is about the keyed construction and verification surface: no secret-dependent behavior in the keyed path that matters for the construction, opaque failure, and constant-time tag comparison. An unkeyed hash API as a whole does not get the same global claim because message length, streaming shape, finalization shape, feature-dispatched kernels, and non-crypto hash/checksum APIs are public-input machinery. “HMAC uses a hash” does not imply “the entire hash API must be globally constant-time under every possible use.”
On “algo/profile negotiation”: this is not TLS-style negotiation. It is closed protocol identifier mapping for RSA profiles: JWT/COSE/TLS/X.509 identifiers are parsed into EXPLICIT supported profile enums, and unsupported/confused algos are rejected. If the word “negotiation” suggested downgrade-prone protocol behavior, I should fix the language, sure, but the feature is not an open negotiation mechanism.
With respect to the perf, if you believe a benchmark win comes from a specific missing constant-time property, name the primitive, input class, backend, and compared impl. “It might be faster because it is insecure” is not evidence... it's negativity and hyperbole.
Also, every single line of this codebase is my responsibility. My usage of an LLM is not a substitute for review, tests, vectors, fuzzing, Miri, and/or a third-party audit. If you're insinuating that using an LLM today is a vulnerability report - I'm afraid you're falling behind the best engineers in the world and are sorely mistaken.
Finally, and this is the one that really gets under my skin, which is likely your goal given your very public history of such... my putting code in public doesn't force cryptographers to review it. It invites scrutiny. If there is duplication that materially increases audit cost, point to it and I will remove or justify it. If there is a side channel, GIVE ME THE PATH and I will treat it as a security issue. But “do not publish until the community has reviewed it” is backwards: review cannot happen against code that is not visible. You're plastered all across the Internet championing this exact maxim, are you not? Did I not read an interview this morning where you went on about the importance of OSS crypto?
Please, take some time to review the codebase or don't; that's up to you... but don't come in here and trash my work improving and unifying well known primitives. Also, this isn't a whimsical codebase vibe coded by Claude overnight. This is a year of my life reading, understanding, and improving the inefficiencies in primitives I've used for years.
The entire point of a pre-v1 review by the community is the same exact thing OSS engineers/contributors have done for decades. I'm looking for the community to point out glaring issues, errors, API shapes, code smells, etc. You came in here with a bunch of incorrect claims and emotion. This codebase isn't perfect, but I assure you - it will be, and it will lead Rust by default becasue there simple will not be a better option.
Happy to talk one on one, or we could put something together where others have a chance to chime in. Either way, have a good one.
Why do the different sha2 variants not share code? This seems like a lot of opportunities for small mistakes/discrepancies; especially considering the many architectures.
The SHA2 variants DO share the compression layer where I felt it mattered:
- SHA-224 uses the SHA-256 compression kernels w/ different IV/output truncation.
- SHA-384 and SHA-512/256 use the SHA-512 compression kernels w/ different IV/output truncation.
There IS some duplicated wrapper/finalization/state code per public type, and I agree that is probably the first place where small discrepancies/mistakes can creep in over time. I appreciate you pointing it out; I've added it to the backlog and will look it over as soon as possible. The reason it exists today is more about keeping monomorphized public types simple. I’m not religious about it; if I can reduce that wrapper duplication w/o making the dispatch/type story worse, I should - and I will.
The guardrail is that SHA2 has official vectors + differential/proptest coverage against the sha2 crate for one-shot and streaming paths.
Yes, I use an LLM daily and have for a few years now. It's used as an assistant during parts of the project, especially for drafting, refactoring passes, test scaffolding, and review prompts. I use an LLM to write markdown files for the public - it's not something I'm great at. I do not treat generated code as trusted... in fact, it's the exact opposite. It has to compile, pass vectors/differentials/fuzz/Miri where applicable, and survive manual review. Also, this is crypto, the tests are not decoration; they are the bar before code counts. I know that our industry is drowning in vibe-coded nonsense; this is not that. This is like a year of my life... and maintaining it for many years to come.
A final point I wanted to leave... this is pre-v1. The point of sharing today was to get people to dig into it and find the problems. If there are other issues, inefficiencies, or smells you fine - please, share them. Thank you!
Hey! Thank you for taking a second. Really, I appreciate it. So... fair criticism. The constant-time line is too compressed and should probably be replaced w/ some kind of matrix.
I ask you to give me a few hours. I'm not able to like devote the time to the comments that it deserves. I'm nearly home, give me a bit, please.
Okay, I finally have a second to breathe. Sorry for the delayed response - life and all that.
I am not claiming the entire crate is constant-time, and if the README reads that way, that is my mistake. My intended claim is MUCH narrower... secret-bearing compare/open/verify/private-op paths avoid secret-dependent early exits where it matters.
NO global constant-time claim for:
- parsers/importers/DER/PHC decoding
- algo/profile negotiation
- keygen and OS randomness paths
- public RSA verification/encryption work
- hashes/checksums/fast hashes as whole APIs
- length/shape rejection before a primitive boundary
- Argon2d/scrypt as blanket CT primitives
With respect to AEAD/MAC verification, the important pieces are full tag comparison and opaque failure. For RSA private ops, the relevant pieces are blinding, fixed-window exponentiation with constant-time table selection, public fault checks, failure accumulation, output clearing, and the release-mode leakage regression gate in CI (rsa.yaml). That is the evidence, it's not proof.
The speedups are not from one trick. This is about a year of work, w/ some general planning before that. The main sources are:
- arch dispatch with portable Rust as the reference path
- hardware AES/SHA/PMULL/CLMUL/CRC/etc. where available and measurably better
- tuned per-size dispatch tables instead of one backend for every length
- fused one-shot paths for small HMAC/HKDF/PBKDF2 cases
- reusable scratch APIs to avoid repeated allocation, especially RSA
- backend-specific kernels for SHA-2/SHA-3/BLAKE3/AEAD/checksums
It is not uniformly faster... but my God, it's close. Crucially, the README.md/OVERVIEW.md call out the losses too: small AEAD overhead, some X25519/RSA verify rows, PBKDF2-SHA256 at iters=1, and platform-sensitive SHA-3/SHAKE behavior... I'm also having some trouble w/ the MacOS Blake3 perf. It's just been elusive af. The `benchmark_results/OVERVIEW.md` is the clearest source for the raw shape of the wins and losses.
My motivation was straightforward - I needed this. My company’s lead product benefits a LOT from removing C libs/FFI, reducing external deps, avoiding competing types, having a unified no_std/WASM story, and making checksums faster. I had worked on https://crates.io/crates/crc-fast previously and wanted to push that kind of direction much further... but it just wasn't going to happen. I contributed the no-std/wasm compat there and then realized... I need to do this myself; that's the point I started like really working out the details. I'd already been exploring it for a while at that point and was tackling Blake3 w/o C-libs head-on for months.
I did consider contributing more to existing Rust crypto libraries, but this was not a small patch series. The shape I wanted was a single pure-Rust primitive stack with small feature-selected/leaf builds, no mandatory C/OpenSSL/system-lib dependency, no_std support, portable fallbacks, and cross arch dispatch built in. The existing Rust crypto ecosystem is important and I use/compare against it heavily; rscrypto is exploring a different packaging/performance/control point.
This is 100% self-funded right now. If/when the OSS side of my startup is ready, rscrypto may become company-maintained, but it will remain open source forever. I cannot afford to start a FIPS validation process yet. I have a backlog, and the first thing - at least before sharing it today - is deciding the FIPS structure if the opportunity presented itself for a subsidized audit.
No formal third-party audit yet, either. I guess I should have been more explicit about that? Current review evidence is purely the public source, official vectors, RSA Wycheproof (I will likely expand Wycheproof when I get a the time), NIST CAVP subset coverage, differential tests against established crates/libs where possible, proptests, fuzzer/corpus replay, Miri, and the RSA CI gate in '.github/workflows/rsa.yaml'.
I’m posting it publicly because I want serious review before pretending it has had one. I know review matters; I simply cannot afford a proper external audit yet, or I would have done it already.
Cryptographer and IACR member with a tiny bit of inside knowledge here.
To me, the entire matter is mostly amusing; the negative impact on IACR is pretty low. I now have to spend 10-15 minutes voting again. No big deal.
It saddens me that Moti Yung is stepping down from his position as an election trustee; in my opinion, this is unwarranted. We have been using Helios voting for some time; this was bound to happen at some point.
Don't forget that the IACR is not a large political body with a decent amount of staff; it's all overworked academics (in academia or corporate) administering IACR in their spare time. Many of them are likely having to review more Eurocrypt submissions than any human could reasonably manage right now. There are structural issues in cryptography, and this event might be a symptom of the structural pressure to work way more than any human should, which is pervasive not just in cryptography, but in all of science.
From what I heard on the grapevine, this scenario was discussed when Helios was adopted; people wanted threshold schemes to avoid this exact scenario from the start, but from the sources I can find, Helios does not support this, or at least it does not make threshold encryption easy. The book Real-World Electronic Voting (2016)[^0] mentions threshold encryption under "Helios Variants and Related Systems", and the original Helios paper (2008)[^1] mentions it as a future direction.
You don't have to tell these academics that usable security is important. Usable security is a vital and accepted aspect of academic cryptography, and pretty much everyone agrees that a system is only as secure as it is usable. The hard part is finding the resources—both financial and personnel-wise—to put this lesson into practice. Studying the security of cryptographic systems and building them are two vastly different skills. Building them is harder, and there are even fewer people doing this.
The article is interesting, but it misses the most practical and unambiguously safe way to generate streams of random data: Use cryptography.
To generate a stream of random data, use a hash function with arbitrary-length output (XOF) such as blake2x[^0] or shake256[^1]. Make sure your key contains at least 256 bits of entropy. Absolutely never use a key with less than 128 bits of entropy.
Since it's impossible to know how much entropy there is in a key, you probably want to use something like the Fortuna RNG[^2]. Substitute the sha2/AES based construction for your XOF. Bruce Schneier designed Fortuna back when XOFs were harder to come by.
If you want more performance, you can use blake2 to compress your input seed into 256 bits and generate the random stream using chacha20[^3].
All of this is usually handled by the Linux kernel[^4], so it's best to just use the getrandom(2)[^5] system call or just read from /dev/urandom[^6]. If you are writing a Rust program, you can use the rand[^7] crate, which uses a mixed approach reading a seed from the operating system and expanding it in-process using chacha[^8]. This is a valid strategy.
I am omitting some subtleties[^10] about mathematical definitions of randomness extractors as used by the author of the article. When you are using a cryptographic approach, you are dealing with a complexity-theory based security notion[^9], which does not precisely equate to creating a stream with a specific amount of entropy. Everywhere – except for a physics or a math paper dealing with information theory – I would call this a technicality. For most intents and purposes, cryptographic security notions are the most real-world robust conceptions of randomness available.
[^10]: In particular, PRFs are not guaranteed to output tokens with a certain amount of entropy – if I recall correctly – because they can map two inputs to the same output.
---
I am the main author of the Rosenpass[^11] post-quantum secure key exchange for WireGuard. My expertise comes from developing this protocol, as well as a couple of years of engagement with the real-world cryptography community and from my own scientific research on cryptography and secure implementations of cryptography.
This is a great post with really valuable resources for any practical attempt to use strong randomness, but aren't you missing the whole point of the article?
Surely if you can just use a strong RNG to generate the key for the cryptographic algorithm you could just use that for all your randomness and ignore the stream of input entirely? The whole point of the article is how to extract the entropy from an unknown/untrusted input stream.
It's like the author has presented a recipe for a chocolate cake and you've said "it's better if you already have a cake, then you can just take a slice of that". Well yes.
Or in the domain of the article, faced with von Neumann's algorithm for getting fair flips from a biased coin, your solution amounts to "Instead I just flip my own coin which I know is fair."
Using hash functions requires a minimum amount of entropy in the seed. So do the schemes put forward in the article. In particular, these schemes require a relatively high degree of certainty about the amount of entropy in the stream at low variation. For the entropy extractors, the amount of total entropy required scales linearly with the length of the output stream. If you are using a hash function, the entropy requirement is constant.
The fact remains, both the method put forward in the post and using hash functions have a minimum entropy requirement. Even if you have only a small amount of entropy, hash functions will still get you more bang for the buck.
If push really comes to shove, you can still use a key stretching function[^0] to make it as hard as possible for an attacker to brute force what little entropy you have, as is routinely done with passwords.
To illustrate the difference in entropy requirement, imagine running the Von Neumann generator for a while, achieving the needed entropy level. In this scenario, the output stream of randomness will be fine. If you then get a section from the stream with very little entropy – much lower than the required amount – you get a section with very little entropy in your output stream. The Von Neumann generator can degrade, hash functions won't (for all practical intents and purposes).
Fortuna is designed to approach the entropy estimation problem. It is eventually secure even in the face of a determined attacker; due to the construction using entropy pools used with exponentially decreasing frequency of use, it covers an extreme range of different entropy levels – around ten orders of magnitude with 32 pools.
Crucially, once a secure level of entropy is reached, the RNG stays secure.
Of course, if the amount of entropy is low, Fortuna will produce quite a bit of insecure entropy before, eventually, producing secure entropy. In practice, this issue can be solved by running Fortuna for a while without producing any output. In a factory producing secure hardware devices, you might just allow the device to be active for a day, and you might some extra, high-quality entropy from the device that flashes your hardware devices in the first place.
Fortuna also allows you to use different sources of entropy securely. Using a Von Neumann generator for instance, achieving this is much harder.
Entropy estimation is borderline impossible in practice; Fortuna deals with this head on, with von Neumann you are lost if your estimate is off.
I am researching formal methods systems to conduct cryptographic proofs; I think a big issue in that space is having syntax sugar in the core compiler, so I am pursuing ways to move syntactic sugar into a separate macro language.
Your post sounds like an interesting research project, but the examples given in the blog post seem somewhat discouraging to me. When I gaze at one of your examples, they seem obtuse and while I am sure I could understand the examples by not just skimming the article, this does tell me that the grammars you used are not very self-explanatory: Your syntax expresses meaning relative to the specific grammar as opposed to expressing meaning relative to the English language or pre-existing, well-established programming languages.
Is there some example of self-explanatory grammars and self-explanatory bootstrapping code in your language?
Self explanatory grammars: there's the stem-like syntax that I showcase and you could write a lisp like syntax in it pretty easily. I'd imagine you don't want to use this in production yet. As for self explanatory bootstrap code: the bootstrap code is the way it is because cognition doesn't want to assume some complex syntax from the start.
I am the Rosenpass author; Rosenpass is an add-on to make WireGuard even more secure. There are certain types of attacks (i.e. those from quantum computers) that could become possible to perform for governments and very large organizations.
To protect the infrastructure of smaller companies and to protect the data of private WireGuard users, Rosenpass makes WireGuard immune against these potential attacks from states and large organization.
Mate, you could just read the code…or give it a try ;)
> the WG author seems like he doesn't care about PQC
This is plainly not true; WG supports post-quantum security with the use of the PSK mechanism as we do.
PQ-crypto is high quality but it is also new and fairly inefficient; not a good thing to integrate into the kernel directly. Using the PSK mechanism is the best way to do this I know of at this point in time.
> It's also not clear how the WG PSK change is coordinated, and whether that entails a brief loss of connectivity - packet loss, latency spike.
WireGuard establishes a session with the existing PSK; we replace the PSK every two minutes but WireGuard keeps its established session around until it renegotiates a session.
Both WG and RP rekey their session every two minutes; there is no interruption.
So is the rosenpass tunnel separate from the non PQC tunnel (the non PQC tunnel being used just for rosenpass)?
Because afaik the moment the PSK is changed all packets immediately start being encrypted by it.
If the change doesn't coincide on both the sender and receiver (within an instant), there will be dropped packets until both PSK's are the same again. Being separate from WG, I don't see how you can insert yourself into their state machine for better coordination.
We are based on the work of Hülsing, Ning, Zimmermann, Weber and Schwabe and in contact with the group. I havn't heard about kuedlki; the slides seem to refer to a reimplementation of the 2020 paper and the go implementation has been stale for two years. They also seem to use a tweaked krystals implementation which I would not trust. The link to the blog post they refer to dis dead.
The Rosenpass protocol builds on the 2020 paper but also adds security against state disruption attacks (CVE-2021-46873). The implementation is actively maintained, written un Rust not in go. We use Classic McEliece and Kyber from the OQS library.
nope, that is not correct. NIST has elected Kyber as one of the algorithms to standardize and we are using that.
As other commenters mentioned (very good info there, thank you all!) the other algorithm we use – Classic McEliece – is one of the oldest algorithms and has been well studied. There is no known efficient attack against it.
DJB says the parameters designed for long term use mceliece6960119 and mceliece6688128 are fine against an attack billions or trillions of times stronger.
I am Karolin Varner, the person who designed the Rosenpass Protocol, which secures WireGuard against quantum attacks, and I am the managing director of Rosenpass e. V. I am well connected in the real-world cryptography scientific community and do cryptography daily.
Based on the responses from the author in this thread, I would strongly advise anyone against using this library for the following reasons:
- Lack of third-party reviews/existing review processes
- Somewhat evasive/defensive answers about LLM usage in this thread
- Lack of complete constant-time cryptography support
- The author's own insistence that this pre-v1 code (if it's pre-v1 and thus not yet at quality, don't advertise it for use. This is cryptography.) This claim is especially alarming since it's not found in the README, which does beg users to employ this library.
- Publishing this and then forcing everyone else to review this is extracting free labor from cryptographers. Code duplication in the code base extracts extra labor from cryptographers checking this. It's simply not good neighborly behavior.
The author claims themselves that the library is not fully constant-time secure; constant-time security is a basic guarantee all cryptography code MUST follow.
AUTHOR QUOTE:
Honestly, I am having a hard time making sense of this comment. A lot of these clearly should be constant time; not making them constant time is by definition insecure.Given that this code is partially LLM-generated, I am dubious about whether every line of the code that is claimed to be constant-time secure is actually so.
It is also very confusing that the author claims constant-time security for MACs but not for (cryptographic) hashes. MACs (message authentication codes) are implemented in terms of hash functions, so how can the MAC be constant-time secure if the hash function is not? It makes no sense.
I suspect that the speedup reported might be in part due to the lack of constant-time security. Constant-time code comes with a performance penalty; if you are not doing constant time and the other people are, then of course your code will be faster.
It is particularly troubling that the author talks about "algo/profile negotiation"; this type of feature was a frequent source of dangerous vulnerabilities in SSL/TLS implementations (look up downgrade attacks). Also, for a library providing primitives, why is algo/profile negotiation even needed?
In short: If you want your projects to be secure, please do not use this. And please, dear author, do not publish half-baked crates in this way. It's disrespectful and steals our time as cryptographers.