 All right, I have opinions and you are stuck with me for the next 20 minutes. I'm Filippo Alsorda, I maintain the Go Cryptography Standard Libraries, which are not the topic of today's talk, but in brief are a complete set of cryptography implementations that go from hashes to signatures all the way up to protocols TLS, SSH. And that's why I care so much about randomness and the point of view of specification and implementation. At the end of the day, I'm an implementer. I am the final consumer of all of the specifications out there. And I am the one who is in pain when those are hard to implement. And most importantly, I am the one that gets things wrong when those don't help me. And when I get things wrong and my laptop has decided to go to sleep, so that's suboptimal. Let me just try to fix that. There we go. Now, you should not trust implementers. I'm an implementer, you should not trust me. And that is how a lot of vulnerabilities come into cryptography. They don't happen because the specification is wrong. They don't happen because of some new break in the underlying math, except isogenes. But they usually happen because we make some mistakes when we're implementing it. So what we're going to talk about today is how can we make the handling of randomness in specifications as implementer-friendly as possible, as testing-friendly as possible, as mistake-friendly as possible, as misuse-resistant as possible. So again, as an introduction, I work on the GoCreator standard libraries. I'm an independent maintainer. And I work for a number of clients, including protocol labs, who have an interest in the health of the Go ecosystem and who are interested in the reciprocal access that we get from the high bandwidth conversations we get to have this way. I also maintain tools like the file encryption tool Aghe and make certification locally trusted certificate generation tool. I've been involved in the design of the GoCheck some database of TLS 1.3 and of Privacy Pass, which apparently was the topic of so many of the talks of this year's River Crypto. So I'm not going to introduce it. Now, the message of this talk is to avoid just making randomness fall from the sky. And that's something that both people in theory, in the theory side, academics and specification authors tend to do a lot. At some point, you need something random. It would be great if there was something that never repeated, was perfectly uniform, and it just appears, right? You just sample a distribution at random and move on. Uniform distribution, easiest thing in the land. No, no, no, no, no. That is actually hard. And we need you to tell us how to do that, as well as making it easy to test things. Why? Well, we're going to start with a negative example. We're going to talk about ECDSA, because over the past few months, I had to refactor our implementation of ECDSA in the Go Standard Library. And I suffered, and so you get to suffer, too. This entire talk, in a sense, exists just because I had to go through that pain, and I want that to not be the case for this new generation of cryptography that's getting specified now, including all of the threshold, post-quantum, and higher-level protocols like NLS specifications. What's wrong with ECDSA? Well, ECDSA essentially says in the spec if you can find it, because you try looking at the NIST spec, and it tells you to look at the ANSI spec, and the ANSI spec is pay-walled. And so what you do is you figure out that, actually, the SCC1 spec happens to be almost word-for-word compatible with the ANSI spec. Nobody really acknowledged that, but that's what you're supposed to use. Great, okay. So you find the spec, and you look at it and it says, oh, at some point you need a random number. So you need the nonce. So that shall not repeat, but good news, it's big enough to select a random. So just pick a random value from the scalar field. Easy. We call the random number from scalar field a Cisco of the kernel, right? There is no such Cisco in the kernel. So instead, we have to do things to pull out a random number. There are different degrees of wrong that can go, and the most wrong is you pick always the same. That is what the PlayStation 3 firmware's assigning process was doing, and they produced multiple ECDSA signatures with the same nonce over different messages. And congratulations, then you get to do math and solve for K. Can you guess what K is? It's the private key. You don't want to solve for the private key. Terrible outcome. They extracted the private key, made their own firmware, and flashed PlayStation 3's with Linux because that had been removed as a feature. So what did ECDSA do wrong to lead to this outcome? We're going to look at this from two sides. One is the properties it wanted from randomness, and one is the form in which it takes randomness. So we're going to start with the properties. And for that, I'm going to paraphrase a bit this famous article, which you might have read, is what color are your bits? The idea of this article is that things are not created equal, and where they come from can still... There are differences between things that look the same. And in this case, it's more about abstract differences, but random bytes can have very different properties. So when I hear that a specification needs something random or needs an IV or needs a nonce, I have no idea what exactly it's asking of me. It could require it to be. No repeating. That's usually the lowest bound. If you reuse the same nonce twice, you're going to have a bad time. We all understand that. I understand that some schemes need that. They need me to not use the same input multiple times. There are some schemes that manage to even be misuse-resistant on that. The ASGCM-SIV, for example, the misuse-resistant AADs, managed to even defend against reuse. But I understand don't give me the same thing twice if I ask you for randomness, fair enough. But it gets much more complicated from there. For example, ECDSA, you might think, as long as you use different nonces for every signature, you're safe. That attack works because you do a subtraction and two equal things cancel out, and suddenly you can solve 4k. But in fact, ECDSA requires much more than that, requires the nonce to also be uniform. If your nonce has a bias, so its top bit is always zero or it's always reduced by the wrong value, lattices happen, and honestly, I have to this date to not understand those attacks, but I'm told that lattices happen and then again the private key falls out. So it's unforgiving. It has to be a completely uniform distribution of random values. Now, that's a much higher request. If you have a bad implementation of a random number generator, it might still manage to eke out something a little different every time, but you might, for example, I don't know, pull the wrong number of bytes out of the generator, or you might do the rejection sampling wrong, and you might end up with a bias, even if you're always using different values. And ECDSA breaks. That's bad, because as I said, implementers make mistakes, and asking them to get uniformness is a much higher bar than asking them to get non-repeatedness. Then there's secrecy. Does a random value need to be secret? Again, in specifications, you don't even know. In specifications, sometimes there's just, oh yeah, just pull some randomness from the ether, let some randomness fall from the sky, and it's randomness. Nobody knows it, and then you stop using it so nobody can learn it. Wrong, because we have side channels and we have memory dumps, and we have all sorts of things that can go wrong. We have weird execution environments where some of your memory is secret and some of it is not, and then somebody comes along and breaks SGX anyway, but that's another story. Still, it's not always easy to keep things secret that you're using, and it's important for us to know whether you need us to keep something secret in the future or not. Then there's unpredictability. Again, you can make something never repeating but very predictable. If it's just an ounce, I can just use one, two, three. That's obviously not uniform, but it's also predictable. Is that okay? Is that not okay? ASCBC, it can't be predictable, and it has to be uniform, but it doesn't have to be secret. You see how there's so many dimensions in what a random value in a cryptography specification can have to be. The first thing I need to know as an implementer is what properties do you need from this randomness? Does it have to be non-repeating? Does it have to be unpredictable? Does it have to be secret? Does it have to be a missing one? There's so many. Uniform. And if we don't know, we are going to suffer. Now, you could say, just run number generator is a solve problem. Just call get random in the linux kernel and there's equivalence in most kernels this day. Just pull that out, then wipe it after you're done, and that should be it. I'm here to tell you both, I agree, and that's what everybody should do. We should not have everybody talking of implementers that deploy to major platform. We should not be reinventing our wheels. We should be using the APIs that we have. But in practice, your implementers will have either weird constraints, difficult platforms, or just inexperience. Because for every specification outer, there's going to be hundreds of implementers. So instead of going implementer to implementer to tell them, here's how you do randomness right, I'm trying to come to the specification outers and tell them, please make randomness as forgiving as possible. However, you can go one step further of just telling me what you need from randomness. What you can do is make your requirements much lower by using something you already have in the protocol. In this case, in case of ECDSA, there is a much better way to do ECDSA, which is RFC 6979 by Thomas Pornet. And what this specification does is say, look, you have a private key, which is secret, a message which is non-repeating for different messages by definition. If the message is the same, then it repeats, but as long as the messages are, yes. So what you can do is take an announce, hash it together with a private key, hash it together with the message, what you get out is going to be uniform because it's the output of a hash function. It's going to be non-repeating for different messages because the message is in there. It's going to be secret because if it's not secret, I have very bad news about your private key anyway. And so you can use that to make even a deterministic signing scheme out of it. Now, do we want deterministic signature schemes or do we want randomized signature schemes? All other conversation. That's not what I'm talking about. This recommends a deterministic scheme. We've learned that that's not actually great because fault attacks and sometimes you get the API wrong and you take the wrong public key with the right private key and you mix those. That was a very interesting EDDSA API vulnerability recently. But point being, you can make a randomized signature scheme by just taking some randomness and then hashing it together with all of these things. What did we gain by doing that? We gained that even if that randomness suddenly has to only be non-repeating for you to get a randomized signature and even if it repeats, everything that happens is that you get a deterministic signature. You see how wide a difference in outcomes this has done. If EDDSA was specified like this from the start, we would not have had so many vulnerabilities. It would not have been possible to jailbreak the playstation 3. Which, anyway. So this is called different ways in different areas. I call it hedging. Some people call it hybrid signatures. There are different ways to call it. The point being, if you have already ways to reduce the requirements of random inputs, please put them in this specification. And don't say, well, that's something implementers can do. You're an implementer. You can just do all this. Put stuff inside the RNG and then use that as your RNG. Well, first, that makes theoretical proof unhappy because suddenly stuff depends on stuff that didn't depend before. And second, you are trusting the implementers. I'm here to tell you not to trust me. I'm here to tell you to instead tell me exactly what to do. If this was part of the specification, we wouldn't have gotten that vulnerability. But it's unlikely to help the fact that it's a technique available when the point is we're trying to avoid implementer mistakes. Great. So this is the part that I care the most about. Hedging at the specification level. If you do have secrets, if you do have no repeating stuff, hash them, tell me exactly how to derive things. And that leads us to the second ask that I have of specifications as regard to randomness. The other problem of having randomness from the sky that falls halfway through a process is that it's very hard to write known answer tests for it. These are tests from the watch proof project. They are just a giant JSON file that gives you inputs and expected outputs, valid or invalid, for various algorithms. And we can use these to test verification of ECSA because, you know, we get the public key and the message and the signature. And if it says invalid, we make sure that we don't accept the signature. But we can't use it to test signing because signing is randomized. Now, am I making an argument for deterministic signatures? I'm not. Randomness can be just another input to a deterministic lower-level function. You can define your randomized signature as a high-level thing that invokes a lower-level core API, let's call it interface, let's call it whatever we want. Calling it with 32 bytes of randomness. And from there on, it's deterministic. And we can write known answer tests for dead layer. And in here, you would just have one other line that says here is the randomness input that we use to produce this signature if it was valid. These are invalid. But the valid ones, we would have the randomness that you're supposed to use to generate that value. And now, suddenly, we can write known answer tests. This is the thing I keep talking about. We want more tests. And tests that we can reuse across implementations. Because again, we can write our own deterministic rng to write our own tests. But then it's probably not the same rng as the other implementation. And so we can't share test vectors. And so they will think about something that I didn't think about. And I'll think about something they didn't think about. And we will not cross-catch these bugs. Bonus points if those tests are even in the spec, of course. Now, that has another advantage. The other advantage is that it specifies exactly how to go from what we actually have, which is 32 bytes of randomness to what you need. ECDSA, again, needs a random value from zero to q. How do you get a random value from zero to q if you just have rng that generates bytes? You look at the spec. It will tell you, right? In fact, the ECDSA spec does tell you two different ways to do it, which, again, makes it hard to test because I need to write test vectors for both. One of them is reduction. The other one is rejection sampling. Now, do I have opinions on which one is better? Yes. Am I going to even tell you? No, because my point is not what specific thing you put in your spec. As long as there is something in this, as long as the spec goes from bytes to bytes, it covers the entire protocol. The other reason to do this is that sometimes people will need determinism where there wasn't determinism. For example, some NPCs. The Zcash NPC, powers of Tao, the one, not the latest one, the previous one, needed, the ceremony needed multiple nodes to derive a number of points on a curve from a seed. And how you derive those became part of the protocol. And how they did that is that they said, well, we take the seed, use it to seed a Cha Cha 20 RNG, and then pass the RNG to our function that takes our RNG and returns a point. And then they told me, hey, we want two independent implementations of this because it's an NPC, so we can avoid the trust in trust issue if you write one in Go that has nothing to do with the Rust one. And I wanted to write it without even looking at the Rust, but then I get to that part. I'm like, wait. So exactly how do I turn random bytes into this? And the answer is no. Wait. So exactly how do I turn random bytes into this? And the answer, that's the order you have to sort the bytes in as they come out of the RNG. Because they get right in one NG, then they go into a trait that takes you in 30 twos and splits them into you in 16s or whatever it was. Those are different NG, and then they get rearranged as limbs by the field implementation in a different enginess, and so you keep flipping and you're playing a three-card game to sort the bytes. And that's part of the protocol now. Yes. So how could this be avoided? By defining in the abstractions, for example, for the elliptic curve that we're using, a way to turn bytes into an element. And that is what RISC Tractor 255 does, for example. That's way too small. Thank you for scrolling off what I wanted to show. There you go. Element derivation. What this says is you have 64 bytes. You want an element of the RISC Tractor 255 field group. And how do you do that? We specify exactly how you're supposed to do that. And exposing these things in the abstractions helps not reinventing the wheel in every spec. And then the specs can say exactly how to go from randomness as bytes to whatever random thing they need. So now a specification that needs a random element of a prime-order group can say we take 64 bytes as input of randomness, and we pass them to this. Ideally, it will even hash them together with some other stuff that's in that protocol, whatever protocol that is, so that they reduce the requirements, because this needs to be uniform, because RISC Tractor 255 doesn't have hashes. It's a very low-level abstraction. But still, what it allows is avoiding that emergent specification effect that we just saw. Another trick I really liked was the classic McLeese private key generation can fail. You can try to make a private key, and it might abort, because it doesn't come out right, I don't know. And when that happens, they specify that you hash the input and try again with the output of the hash. Now, why is this clever? Because you do that once, but then to serialize the key, you just take the final value you used and you save that on disk. Key generation and key deserialization are the same function. Simply, key generation might have to reissue, while key deserialization knows that it's probably not going to reissue, because when you were doing key generation, you did the retries and eventually saved only the final value. I really like that trick, and I like it because it tells me exactly how to go from bytes to my random output. And speaking of key generation, that's the last thing I want to cover. Key generation is one of the most important things you can get wrong, because you get key generation wrong, you are unlikely to succeed at anything else. I mean, you're likely to have anything else being secure. And yet, we have basically no known answer tests for any key generation algorithm. ECDSA just tells you, yeah, you just pick a random scalar and then multiply it by the base point, and now you have a public key. Congratulations. Okay. Do I do rejection sampling? If I'm doing rejection sampling, can it fail? Well, on p56, yes. It's 2 to the minus 32. That's entirely possible. On p521, no. It's 2 to the minus 200 something. So for p56, you could write a known answer test for rejection sampling failing, and that is what we did in our code. But it's just a test that we had to write for ourselves. You see that we have a little hook here to notice that looped, and we are asserting the test that we are observing a loop where we know it's possible. But it's a thing we wrote for ourselves. Is it correct? I think it's correct, but it would be much better if it was in a set of known answer tests, because again, this is something where randomness just falls from the sky. Instead, I very much want randomness in specifications to be specified from deterministically from a set of random bytes all the way to the output. And I think this is pretty much the only other important thing is how large that value is. The AADs get this wrong by asking us for a non-repeating value and then giving us 96 bits of space. And so we're having all these very exciting conversations about, okay, how many messages can I actually encrypt before randomly picking that becomes a problem? And we all take a guess, and then five years later, we're obviously using the system for something else, and we have to go back and say, oh, huh. Well, that's not going to work. So yes, make the values large. Make them possible to select randomly, but then require only known repeatedness, and we will make way, way, way fewer mistakes. Thank you very much, and if there are any questions, happy to answer them. Now we're in the break. I mean, so far you mentioned, of course, this well-known mistake between kind of non-repeating and truly random. I'm just curious if, in your experience, you encountered something where people did get the non-repeating part right, but kind of in practice, the system fell because it was not truly uniformly random, because there is a big gap between non-repeating and uniformly random. There is indeed. So I don't want to keep just pouncing on the ECDSA, but that has been exploited on the Bitcoin blockchain. The Bitcoin blockchain can be thought of as the biggest collection of ECDSA signatures also sometimes objectionable implementations, and there are attacks that use lattices to extract the recovered annonces when the annonces have some bias, even if the annonces don't actually repeat. What's hard there is that when they repeat, you see it in the signature, because half of the signature repeats when the nonce repeats. If it's non-repeating, you can't see it, but if you know that those are from the same implementation that has some bias, then you can do lattices and things fall out of it. I'm thinking of another case where I remember things where... I think I remember a couple of cases where the other way around happened, where things only needed non-repeatedness and things went a little wrong, but at least there was a little bit that was different, because maybe the time was in there, and now do we like that? No, no, no, we don't want to rely on time for entropy, but sometimes that saves you, because I guess that's predictable and everything else, but there's a time stamp in it, and we weren't using it at high enough rate that was still non-repeating, and so we're safe. That's a very good result for our specification to fail so gracefully. So I strongly agree with both de-randomize your signatures and use system randomness in them. I would actually say something even more extreme, which is you should try and make... you should kind of hide the input for the system randomness, so make it an optional input and then have your signing method actually go to the random, or something like this, or call it an optional test vector random, something like that. I was talking about what to do for specifications. If you're instead writing APIs, you should probably not even talk about randomness in your external API, and when your sign gets called, it goes to get random, gets a randomness, and calls the lower level sign function, and that one is the one that you test with the known answer tests. The important thing is that the specification still needs to have a clean abstraction break at that layer with the randomness as a well-specified deterministic input, well, non-deterministic, but specified input, so that then you can deterministically test that function. But I agree that the best APIs, high-level APIs, don't ask the randomness to the final user. One of the things, one of the very funny things that saves is sometimes there are devs who will think they can have secret keys in a context where they don't have system randomness, and this catches them, and then, you know, it prevents all kinds of bizarre thing. Yeah, I agree, and that's another reason I want inputs that might be conceivably be random to be as large enough to be always random, because for AADs, I can't do that because I don't know if my user is going to encrypt a million messages, in which case random 96 bits are fine, or a few billion messages, in which case, no, I can't generate it for them. So that's why it's important to make things large enough that the birthday bound is not a concern. Cool. Well, thank you very much, Philip. Thank you. We were told.