ZK Whiteboard Sessions - S3M1: How to Build Hash Functions, with Jean-Philippe (JP) Aumasson
2 Sept 2025 · 1:14:50
Jean-Philippe Aumasson, co-founder and chief security officer at Taurus and designer of the BLAKE hash function family, walks Nico Mohnblatt through how cryptographic hash functions are built in this Zero Knowledge Whiteboard Sessions module. The talk covers the sponge and Merkle-Damgard construction methods, security definitions and bits of security, and how algebraic hash functions like Poseidon are designed to be efficient inside zero-knowledge circuits.
Speakers
Topics
Transcript
[Music] Welcome to this module of the ZK Whiteboard Sessions. I'm Nico, I'll be your host today, and our guest is JP. Hi, JP. Hi, thanks for having me. It's great to have you because we're going to be talking about hash functions, and you are one of the designers of BLAKE, right? Yes, BLAKE, the BLAKE hash function and the subsequent versions, BLAKE 2 and BLAKE 3. Nice. So what's the agenda for today? So we're going to talk about hash functions, cryptographic hash functions. So we're going to start with what is a hash function, what does it do, why, and then what does it mean for a hash function to be secure? There's not one single notion of security.
And then we're going to see how to construct a hash function, how does it look like under the hood, from the mode to the algorithm. And I'll also give you a few examples of hash functions. And then we'll try to explore what does it mean for a hash function to be a good or useful hash function? What does it mean to be efficient? For what, for whom? And then we'll explore a bit more of security. How can you attack a hash function? I'm not going to give you a recipe about how to break any hash function, but the general strategy and particularly differential cryptanalysis. And finally, we'll move on to the ZK part of this podcast.
We're going to talk about ZK-friendly hash functions, also called algebraic hash functions like Poseidon, and maybe conclude with Fiat-Shamir, and where you use hash functions. Sounds great. So let's start. So hash functions. So how do we spell hash functions? H-A-S-H. And start. And function. Function. Okay. So first of all, again, it's cryptographic hash functions. If you look on the internet, there's also hash functions as used for databases, for other purposes, which do not have the security features of a hash function.
So hash functions, we are recording this in Lausanne, Switzerland. They're also called the Swiss army knife of cryptography because they're really ubiquitous. They're everywhere in your pocket. You have a hash function, you have hash functions all over the place. For example, you have hash functions in signatures, signature schemes, because when you sign a blockchain transaction with ECDSA or 8025, you don't directly process the message, you process a hash of the message, because it gives you something of fixed size, and that's what your signature scheme will process. So by extension, also in certificate, chain certificate management, where you essentially have a kind of blockchain in a certification.
Hash functions, they are also used in, for example, RNGs, random number generators or random bit generators. For example, in the Linux kernel, the hash function is used, specifically BLAKE2, for processing the entropy pool. Also used in KDF constructions. So KDF stands for Key Derivation Functions, an object that derives keys from source material. For example, HKDF. So guess what the hash stands for.
And also PRF, so Pseudo Random Functions, which is just a fancy way to say hash function, where you have a key, a secret. So to compute the result, you need to know that key. If you don't know that key, you can't compute the result. And PRFs also give you a secure MAC message authentication code. So if you have a secure PRF, you can use it as a MAC, but not the other way, not always. And also in ZKS-NOX, you have all the cryptography that exists, except maybe cyphers, but all this commitment and whatnot.
ZK-Erasing, Fiat-Shamiring, Merkle-Trees. So even if there's something missing here, we don't mention cyphers, encryption, decryption. It's only about integrity, data integrity, and not confidentiality, or very rarely or in weird cases. Okay, so that's what hash functions are about. But if you've never seen hash functions, you might be like, okay, what does a hash function do? So let's show a hash function from a very abstract perspective.
So when I give crypto classes, I first tell people, what is an RMG, a deterministic Random Number Generator, where you start from a seed or a key of fixed size, and you generate an output of arbitrary size. And they usually say that a hash function is kind of the opposite. So I'm going to draw a lozenge. Okay, so that is my artistic view of a hash function. It's a transformation. It maps an input. I'm going to write it m, like message. And the output, I'm going to write it h, small h, like hash, also called, well, hash value, fingerprint, checksum, digest as well. But the industry standard is h, which is very confusing.
So you have h of m equals h. That's the simplest thing you can imagine. One input, one output. So how do they differ? So m, a mathematician would write that it belongs to the set of all bit strings of arbitrary size, even the empty string. I guess there's a one there. I'm missing a one, yes. Nice. And this output value belongs to the set of bits of a fixed size that I'm going to write n. So we go from any message, even the empty message, you can hash one byte, one megabyte, one gigabyte, however many bytes you want. But you're always going
to get something of the same size. Always n bits. Always n bits. I'm going to ask, what is n? Yes. So it can be whatever you want. But if you want it to be secure, typical choices for n are 256 bits. I'm going to explain why later. You can make it longer, maybe 512 or even more, if for some reason you need the hash to be longer. For example, well, I said before that I should not do, you choose an encryption, but I was lying. So in RSA or AEP, it's a mode to use RSA.
In this construction, you use a hash function called the mask generation function, something like that, which is instantiated with a hash function. And in that case, you need the hash to be a bit longer than that, maybe 1000 bits or something. So that's one example where you need a long hash. Anyway, so it's tempting to think, oh, the longer it is, the more secure it is, because the number is bigger. It makes me feel better. Yeah, it also feels like it's easier to go from something big to something bigger, you know, like... Yeah, you compress data. So in terms of information theory, in terms of thermodynamics, you lose information. Yeah.
So hash functions also use a one-way function. It's also called a one-way function, because if you lose information, you go from something gigantic, you can't go back. And so there's something called the pigeonhole principle. I don't know how to draw a pigeon, so I'm not going to try to. But the idea is if you have like, I don't know, four boxes and five pigeons. So those are pigeons. Right. See, that's the wing, the head. Yeah, yeah.
And you want to squeeze those stupid pigeons in those four boxes. So however you do it, you're always going to have at least one box with two or more pigeons. So if you try to do it like this, like, okay, and to put it there. So if your output space is smaller than your input space, inevitably, you're going to have what cryptographers call... Collisions. You're following. So a collision is a pair of messages, M1 and M2, which satisfy two conditions. So the first one is very complex. It's called difference.
They're not the same. And the second one is also pretty straightforward. They have the same hash value. So spoiler, that will be something that you want to avoid. But what does it mean to avoid? It should be harder to find those messages, but inevitably, they exist. And you have plenty of them because this is much bigger than that. So we live with that. But from the perspective of the attacker and from the perspective of their time, budget, computer size, universe, physical limitations, B not being equal to NP, it should be hard.
Which brings us to the security definitions of a hash function. What does that mean to be secure? M is big, H is small, M is of any size. I'm going to keep this. I'm going to write this out. So this is maybe the most important part of the presentation. Because when you hear about what makes a hash function secure, oh, collision, preimage, second preimage. What does that mean? So preimage is the problem where you are given an output H, a real output H with a real value.
And you're given a challenge to find some M such that H of M is equal to H. Then you can ask, okay, how is H chosen? Is it randomly? What is the distribution? But from my perspective, this kind of misses the water compared to the fundamental notion of security of your hash function. These are just consequences of the single definition, which is the following. So you want that thing, that transformation, which we see as a black box, except that it's white. And it should behave. So behave means what you observe as an observer, as a user.
It should behave in the same way than a randomly chosen function. So I said randomly chosen, not random, because this thing is deterministic. It's not probabilistic. You give it the M twice, you get the same H. But if you chose it as random among all the possible functions that map an input to this kind of output, the function will have no specific pattern. If you flip one bit here, this thing will be completely different.
It will be like if you chose a new value randomly. So a consequence of that, if you don't have any pattern, any structure, there's no shortcut, no backdoor, nothing whatsoever. One consequence is that this problem, the primage problem, is hard. Because H is some function, I don't know what it is. You have not a way to solve that. So finding M from H, then by repeatedly trying like an idiot again and again different messages.
So writing out the whole function essentially, like the table of the whole function, right? Yeah, so you don't need to store it in memory because you can fire and forget. But you try M, you try another one, until you get lucky. Now we're going to do advanced mathematics. What is the probability? What is the expected number of attempts until you find the right one? So you have 2 to the N possible values of H, because it's a string of N bits. So if N is large, this is very large, exponential. So you try, you try, you try, you try. And on average, you're going to hit the lucky value after maybe half the possible values.
So why is that the case? It's some quite basic probabilities. Okay, you have one chance over two. So the expected value in that case will be 2 to the N over 2, which you can just write at 2 to the N minus 1. And if N equals 256, and if we do a subtraction, it gives you 255, which is huge. Yes. And even if N were only 128, it would be 2 to the 127. Still gigantic. Which is still gigantic.
And if the effort is that amount of computation, whatever the computation is, you're safe. Yeah, it's never going to happen. In comparison, I think the number of operations to mine one Bitcoin block could be close to 2 to the 80 hashes. Yeah, that's what I remember. And if you count the bit operations of all the SHA-256 you do, maybe you get closer to 2 to the 90. It's exponential, so it grows. Yeah, for some reason, a huge factor.
So here you need 2 to the N attempts, so you're going to take quite some time. Now for collisions, it's different. Collision, people think, oh yeah, birthday paradox. But the birthday paradox, I'm not a mathematician, so I like things explained in a very simpler way. For my brain to process it. So here, spoiler, the answer will be 2 to the N over 2.
Okay. Which is equal to the square root of 2 to the N. And why? Why is it that smaller? Why is it quadratically smaller than that? Because if N is 256, then you end up with 128. So instead of this. So the reason is the following. Here you're not looking for one message, you're looking for two messages. So you're looking for a pair of messages. So let's say you try going to introduce the symbol N. N is the number of messages you try.
So you have M1, M2, blah, blah, blah, until M to the N. So if you just have those messages, well, you have N messages, but what you're looking for is a pair. So how many pairs do you have here? That's going to be capital N squared, right? Yeah, the order of capital N squared. So technically it's N times N minus 1, or 2, but it's approximately N squared. And now what we want, what we want, I'm going to write here, is we want to have N squared approximately equal to 2 to the N.
Mm-hmm. So if you flip the quadratic, it means that N must be square root of 2 to the N. Okay. So, yeah, prim edge, collisions. But you're saying these two are consequences of the fact... They are consequences of the fact that the function behaves like a random one. And... A randomly chosen function. Like a randomly chosen, yeah. You fell in the trap that I did not create intentionally. And another consequence of that is the following, and we'll get back to this later. Any transformation...
So let's say, instead of this being infinite, we say just a really huge value, whatever N, which is much bigger than N. And here we still have N, whatever. So whatever this function is, there's always a way to describe it as equations. So here, let's say that each edge, h1, h2, those are the bits of this guy of h. So you have this up to h, hN, right? So you can write h1 as a function of the bits of this guy, f1 of m.
And since you have all those bits, it's going to look like a combination of additions and multiplication, what ZK people also call a polynomial. So it might be m1 times m2 plus n1 times m2 times m3 plus blah, blah, blah, blah. And on average, for a random function, if you write this equation down, each of the possible terms will appear with a probability 1 over 2. So you see, if we have N bits, you have m1, m2, m3, m4, up to mN. So you have so many combinations of those bits. And the equations will be gigantic because you have an exponential number of...
I mean, can we see this as having capital N variables in this polynomial? Yeah, exactly. So that's huge. So here I did it with bits because it's simplest, but you can consider it with respect to whatever is your favourite finite field, whatever your favourite encoding. But in any case, the equation should be huge, hard to find. And there's two dimensions here to consider. The first one is the degree. The degree of the polynomial. The degree is the maximum number of terms in a monomial and the density of the equation.
So how many monomials? Exactly, how many terms. And if the density is low, which means the equation will be sparse, it's easier to attack. And also if all those equations are kind of correlated in some way, it's not what you expect from a randomly chosen function. Right. That's something you might be able to exploit. But if those questions are really random looking, a consequence would be that pre-imagined collisions would be super hard to find. Okay. Okay, let's move on to how that box... Yeah, how do we make these? Yeah. We have this magical object. Yeah, it's not that magic, it's pretty boring.
Okay, super, I'm gonna get rid of it. Okay, people got what's a hash function. So we want to process a message of undefined arbitrary size. It can be one bit, it can be zero bit, it can be a gazillion bits. How do you do that? Divide and conquer. So you break your message down into different blocks. So M will be the concatenation of blocks. So M1, M2, until whatever is your last block. And each block will be of the same size. So concretely, those can be of, for example, 1024 bits. Or more or less, it depends.
So we start with something that has an undefined length? Yeah. Arbitrary length. And it could be any size. Yeah. So here you might have... Sorry, just to make sure we have... So these chunks that are fixed length. Yeah. But we might have like some variable number of them. Exactly, so there can be... And you might also wonder, yeah, but what if M is not... If the size of M is not a multiple of the chunk size? There's a little trick. A little padding trick to add filler bits to the last chunk. Okay. So there's different ways to do it. But typically, you might add 00s and maybe an encoding of the size of the message. Which can be the bit size here.
There's a few things to get right, which is super important. But it's pretty boring. So I'm not going to detail that. Okay, so now we're going to process the chunks one by one. So you start from the beginning. You have the first box. You see now we're doing a square, not an orange. You'll see why. And then what's entering to this box would be the first message. Then you get an output, another box, another message, chunk, yada yada. And you repeat it again and again. And at the end, you get the final hash value.
That's oversimplified because you're not seeing how this box is working. What is the box? What is the box? It's a secret. Okay. I can't tell a secret. And you start from the value that we might call H0. So this value that is modified iteratively by the message blocks is sometimes called the chaining value. Because it's a chain. You see, it's like a blockchain. It's a chain of blocks. Blockchain before Bitcoin. Also called the state because it's stateful, obviously. If you have a different message block here, that value will be different. Unless you're really not lucky.
So what you want is that this guy depends on each single bit from each of those blocks. Yeah, that's how all hash functions work. But it doesn't tell you how the transform is done. So there's two ways to do it. So I'm going to write scientific terms here. The first one is actually called the Sponge. And it is based on a permutation. A permutation.
Okay. I'm guessing you're going to explain what this means in a second. Yep. Okay. Permutation. And the second one, it's sometimes called the Merkle-Damgård construction. Because those two guys, they had the same idea around the same time in the 80s. Is it the same Merkle as the trees? Exactly. Ralph Merkle. Yeah. He was really into hashing. And those are based on a keyed permutation. Oh, sounds more complex. Yeah. Because we have a key. We have a secret.
But I told you there's no secret in a hash function. What is also a keyed permutation? Are you asking me? Yes. Oh, I'm not sure I know. Not sure. So it's also a block cypher. So you see, you can create cyphers from hashes and you can create hashes from cyphers. It's so intertwined. I'm going to start with the first one, the sponge. So this came, I think, in the early, mid 2000s, 2008, something like that. And this was formalised and analysed by Johan Dement, one of the authors of AES.
And they did a really series of papers to build a very theoretical framework, a very convincing resource why it's not completely done. So how does it work? First, what is a permutation? It's typically abbreviated P. You know, we are creatives in crypto. P like permutation. So permutation is something that takes, let's say, a state S, and it gives you another state S' of the same size in such a way that you can compute the inverse. Oh, S' and get back to S. So you don't lose information.
It is a bijection. You can go whichever way you want. In practice, you only need to implement the forward permutation. So does that mean a permutation, are we flipping around the order of bits? Or are we... So it's not a permutation. Yeah, people often get confused by this. Like, oh, we permute the bits. No, what is permuted is the actual state as a value. It's not about the encoding or the bits. So you see this like a gigantic number. And for example, one number is mapped to another one, but there is no collision. So this state S, it's a series of bits. In Keccak, SHA-3 is typically 1600.
And we see it as two parts. I'm going to explain why later. So we draw two wires into P. But P already sees it as one single input, which is the concatenation of the two. Why? Not just to make it more complicated, but because we're going to do that. We're going to take our M1, our first message block, and inject it, add it, XOR it to this part of the state, which will then get permuted. Okay, so this symbol here means XOR? So here's the XOR, because most of the time it's an XOR when you deal with bits.
You just want it to be invertible. So XOR is the easiest for most computers. So that's how Keccak works. But you may work with any other operation that satisfies the requirements. So when you talk about Poseidon, it won't be XOR. And it's very, very simple. And then you XOR another message block, a different one, and you permute again. And when you want an output value, you extract that value and it becomes your hash.
So extract just means reading this wire? Exactly, just reading this. And if you want more, you can have an H2, whatever. It looks super simple, right? It's actually quite simple. There are some tiny details to get right. It's also been called this approach, so a sponge. What a sponge is doing, if you have a sponge in your bathroom, it absorbs water and you can squeeze the sponge and water leaks out. So that construction was called the absorb and squeeze. So absorbing on this side and squeezing on the other.
You absorb the water here and you squeeze different water out there. And yeah, that's pretty simple. And based on this idea, you can also generate other things like pseudorandom functions, where you first introduce, inject a key and then your message. You can create a deterministic random number generator. You can create output, hash output of variable size. So it's very flexible. And it's quite simple because you only have to design a permutation that takes one input and gives you one output of the same size. So, Keccak is a sponge function. It was a hash function submitted to the SHA-3 competition.
And it was selected as the foundation for SHA-3. So now if you work with Ethereum, if you implemented an EVM, you know that Keccak is not exactly the same. It's 99% the same, except for the padding. So the padding is the way you fill the last bits to get a chunk. So yeah, those are sponges. It's pretty cool. And Keccak, SHA-3 and Poseidon, of course, is a sponge.
I do have a quick question and maybe we'll get to this later. How do we pick our permutation? And how do we know that the overall thing is secure? Because if you tell me this is going to behave like a random function or a randomly chosen function, I'm not sure I'm going to believe you at this point. Yes, so yeah, that's a good question. I'm going to answer first to the second one. So when you worry about the security of a hash function, you worry about the security of two things. The first one is the mode. So the mode is this construction. And the mode will assume that this thing is perfect for some very precise definition of perfect. So we don't worry about P. We assume it's perfect, it's a perfect permutation. So you're going to prove that this mode is secure, where secure
means that there won't be any way to compute preimages or collisions faster than ideally. And this security notion that applies to the mode is called indifferentiability from a random oracle. So random oracle is really an idealised view of a hash function. Idealised even more than picking a random function, but idealised in the sense that anytime you give an input, you have some guy in the background picking a truly random, random as in quantum physics, output.
So what this notion of indifferentiability gets you is the guarantee that if P is secure, is properly designed, then the mode will be safe against collision attack, precision attack, and whatever other attacks you can imagine. Any attack in terms of correlations between input and output, you can be, for example, interested in attacks where you want this output to have a specific correlation with input or to have some number of leading zeros here and number of leading ones there. Anything that is statistically unlikely and that you want to find.
So that's indifferentiability. I'm going to write it because it's quite a mouthful. Indifferentiability. Sometimes we also talk about indistinguishability. Is that different? Yes, that's different. So, cool. Yeah, it's not confusing at all, right? So indistinguishability, so small interlude, it's a notion that applies to keyed hash function where you have a key. I'm going to write it here. So instead of just hashing m, you hash a key and the message.
So the key is secret. And let's say, as an attacker, you can query this box where you don't know the key. You send an m and you get the output. You can't compute it or see yourself because you don't know the key. So you know the key, you're going to give me the output and I do it a gazillion times. But the trick is the following. When you give me the output, you flip a coin and either you give me the output of the function or you give me a random value. And you remember this random value because if I send the same message, you're going to send the same value. Now, my goal is to distinguish the cases where you give me the correctly computed output from the cases where you flip a coin and give me something completely random.
So that's really important because most of the security notions of symmetric cryptography is about emulating randomness. Doing something that looks like garbage that is semantically secure in the sense that there is nothing that really makes it different from an absolutely random thing. So this is indistinguishability. But it's just for keyed hash functions. Yes. And I guess it's not necessary for our topic today. Hold on.
Sorry. I need to focus. Yeah, yeah. The spelling here is crazy. Indistinguishability. Sorry for my writing. No, no, don't worry. Yeah. Sorry for the long words. Indistinguishability. And what else we have? Yeah, and there's other notions. So we mentioned random oracle, ideal cypher. Yeah. So we try to idealise things and try to make something that outwardly from an observer's perspective is not very different. Yeah. Sorry, I took you on a tangent with indistinguishability. But indifferentiability, we said, I can't make the difference between this thing, this construction, and a random oracle, you said.
Yeah. So if you have a function that does that with a perfect p, and you get only the outputs, you can't say, oh, I'm sure this guy comes from this construction and not from a random oracle because of some structural properties. No. It will behave completely random. And intuitively, well, it's not very intuitive, but if you get it, it starts to make sense. Yeah. But otherwise, yes, kind of confusing.
Okay. And these proofs of indistinguishability, do they depend on some assumption of hardness? So the assumption is just optimism. Okay. Just saying that, okay, my job is to secure the permutation. Then your guy's job is to create a secure p. Yeah. So this is a different team, so to speak. Okay. That will create a transformation which behaves well, which ultimately means being described as equations that are very long, very high degree, very dense, and doing so in a simple way. So it's trivial to create a good permutation.
You just take a lot of random equations. You make computation that takes two hours. Yeah. But we want it to be fast and simple and programmable. As in easy to compute. Yeah. And it's not enough to do something that just looks like complete garbage. It's so confusing. It must be random. No, you need to have a rationale behind. You need to be able to convince someone that it is resistant to this type of attack and okay. So yeah, that was kind of an answer to your first question, but we'll get back to this later.
Sounds good. Now let's move to the number two, the other construction, which is the legacy from the 80s. Well, it's pretty good. And there's, first of all, there's none of those two which is intrinsically superior to the others. It's different trade-offs. To be fully honest, permutation-based is easier. But as you mentioned, the functional design, BLAKE, BLAKE 3, they're not permutation-based because reasons. But yeah, ultimately it's very similar. Good to describe how this works. Sounds good. So the other approach is less flexible. In some ways, it's simpler.
In some other ways, it's not. So you also start from an initial value, sometimes called the IV. And here you're going to do something weird. Sorry, I'll just quickly interrupt you. I guess the intern, this value is necessary in case we're hashing the empty message, right? Like if we're hashing an empty message and we still want some output, we need to start from something, right? Exactly. So yeah, if you hash the empty message, it's funny because when we submitted hash functions to the NIST competition, one of the criteria was to support empty messages. I'm like, who the hell is hashing empty messages?
Okay, you never know. So it's empty, but it's not completely empty because the first block, M1, is filled with padding. So it will say, hey, I have a message, but its size is zero. So you do that. And this thing here, you'll do this little trick. An XOR. And here you'll do the same thing. And then put another message block.
And if your message is just two blocks long, that's what gives you the output. Okay, so what do we have here? Man, I really suck at drawing those things. So this is an XOR. So here, this means that you take the initial value, the input value before applying the transformation, and you XOR it. So you memorise it, and then you XOR it, and then you forget it. I'm gonna call this B, like block cypher. Because it's essentially a block cypher. It behaves like a block cypher. It's kind of designed like one, except that you may not use it to encrypt things.
Why is it a block cypher? Because it's a keyed permutation. The small triangle here means that this input will be the input for the key. Okay, so M1, H0, XOR H0, gives you H1. Okay, this will be H1 here. So why do we do that? First of all, what is a keyed permutation? If you fix this value, then for any input, you have a single output, and B is reversible. It's a block cypher.
You can decrypt, you can go this way, that way. So if I fix the key, then I get a regular permutation. Exactly, you get a regular permutation. So doing this little trick here prevents you from going backwards if you know M. So if you know H1 here, then you cannot compute backwards, because you don't know the plain text in the first place, and you can't guess it. So this is the approach that was used in old hash functions like MD5, MD5, like SHA-1, and like the SHA-2 family, and also like, I can't spell my old hash function, like 2, like 2, okay, and 3 to some extent.
So for example, in SHA-1, you had a hash value of 160 bits, which means that this guy was of 160 bits, and the output was of the same size. So you see the state is always the same size, because you just permute it around and exhort it. The message chunks could be of different lengths? They're all the same size. I mean, sorry, are they also 160 bits? I don't know, they can be whatever. They're typically larger. They're typically larger than an actual symmetric key.
There might be 512 bits. Okay. And yeah, that's it. So if you have a block cypher that is wide enough, you can do that. But typically, cryptographers haven't been directly using a block cypher designed to be a block cypher as a hash function. Why? Because the block size was too short. Block size is this 160? Yes. Yeah. So like MD5, it was of size 128, which means that you could find collisions in 2 to the 64.
Yeah, that's not great. Yeah. So yeah, that's quite simple. Here, I draw the version where M is used as a key, and you have this little thing here where you exhort the initial value. But there are other combinations of exhorting things around. I think if you also want to hash the message here, I think it also works. There are some ways that work, some ways that don't work so well. But this happens to be the most used because it's simple enough. And it's, yeah.
I guess same question here as last time. I can look at this diagram, but why would this behave like a randomly chosen function? Because cryptographers say so. Okay. They have written plenty of papers. Now, the real answer is, again, you studied from two levels, from the mode perspective. Yeah. And here, you assume that this be a perfect block cypher, an ideal cypher, so to speak. And you don't worry about its internals. And you prove that if you do it right, it's secure, it's indifferentiable from a random oracle. But sometimes that needs a few more twists.
So you need a good padding. And you also need those functions to be slightly different. Okay. So sometimes it's done by injecting another value, like a counter. So here you have one, here you have two. To have this high level of probable security, you also need this function to be different. For example, if you look at BLAKE2, we had to do that to satisfy the highest level of theoretical security. So, yeah. The disadvantage compared to the sponge, as you can see, is that here you get one single input.
One single, sorry, one single output. You don't get to choose the size of the output. You don't get to generate more output. Yeah. There's a few tricks to do that. You can say, oh, I'm going to hash this again and again and again. Which is kind of what we did with BLAKE. But it doesn't really come out as naturally as with the sponge. It's not as clean. But again, you don't worry about elegance. You worry about speed, about security. Okay. Let's briefly mention the quantum attacks, so to speak. Well, the impact of quantum computing on hash functions. So, again, as you know, we're talking about hypothetical, actual quantum computers that could do things that they can do right now.
But they might exist in the future. Some future, yeah. Yeah, in five years. I mean, I always say in five years. It's always in five years. Yeah. So, it's, again, simple, but not that simple. So, we mentioned that for pre-image, the classical way to attack it by trying one input after the other will take you approximately 2 to the n operations. Now, on paper, with a minus one on the expected value, with a quantum computer, you have this search algorithm of Grover, so the Grover quantum algorithm, which, with some kind of magic,
brings down the search in a list, the search in a database, to a complexity that is of the order of square root of the size of the table, which in our case means square root of 2 to the n. So, like, oh my god, it's much bigger because we go from this to that. So, there's two things to consider here. So, it's a big O, it's an order of magnitude. There's a constant factor, which might be gigantic. It might be 10 billion times that.
It also involves the implementation of the function as a quantum circuit. And if you implement, for example, AES as a quantum circuit, the last estimates I've seen need a circuit of depth approximately 2 to the power 70. So, you need a gigantic quantum circuit that needs a pretty solid quantum computer, and it takes some computation time. So, you need the coherence for quite a long time. So, it's not just about theoretical time, it's about the hidden factors, and it's about the cost in terms of energy, time, engineering. So, it won't be, in terms of cost efficiency, a square
root of what you would do here, because this is easily parallelised on millions of GPUs. Anyway, it's not unlikely that, effectively, this ends up being easier, whatever easier means than that. But if you have a hash function of 2 to the 156 bits, and the worst, worst, worst case, you're down to 128, which you're still on the safe side. For collision, again, it's a bit tricky, because in theory, so it's 2 to the n over 2, really, approximately. With a classical.
With a classical. And quantum, there are some algorithms, like the BHT, which tell you that it's this order of magnitude, so n divided by 3, instead of divided by 2, which means that this number is smaller. However, it also needs quite a large circuit and some memory. I bet the memory might be, maybe, of the same order of magnitude. Not fully sure. But there was some debate about whether the quantum collision finding algorithm would effectively be more interesting than this one. Because, again, this can be parallelised in a way that uses very little memory.
So from what I've read, I believe that this will still be more efficient than whatever quantum. But you never know. You don't know how quantum hardware will look like in 200 years. And worst case, can we just make the length of our hash greater? So just make it a bit bigger. But I think if I teleport myself 500 years from now, my guess is that 256 will still be good. But people call me optimistic. So here the upshot is that the impact of quantum computers is not as bad as it is for, let's say, factoring or discrete log-based things like ECDSA, RSA,
Groth16, or whatever. Okay, so now what is a... Let me also get rid of... We wanted to briefly talk about what is a cool hash function. What is a good hash function? So you're designing a system. You're an engineer. So if you're an engineer, you have to understand what you need before selecting something in terms of functionality, in terms of speed, in terms of security, in terms of integration time, maybe in terms of integral property, and in terms of, oh, I already have this code.
So maybe I'm going to use what I already have instead of choosing something else. But let's put aside all those engineering concerns and really focus on two things. The first one is security. Go there. If you don't need security, maybe you don't need a hash function in the first place. And the second one is efficiency. So is this how much energy I'm expending per hash computation? So in some way, but it usually boils down to speed. So how much time you wait between when you start the computation and when you finish it. Okay.
Which is the simplest way to see speed, how much time it takes, how long I have to wait. Now speed can mean more things than that. Speed for what? Do you hash a lot of small messages? Do you hash one gigantic, one huge message or many huge messages? Because the speed might be different. For example, a few years ago, we designed a small kid hash function that was called SipHash, which is not a hash, but it was really optimised for small messages.
That's the first thing you consider. And then how do you implement it? Can you pipeline? So you start hashing here one message, you hash, but shortly after you start hashing another one before this one completes. So can you pipeline the operations? So this brings us to the considerations around parallelism. So I just like doing a few things at the same time. You can see parallelism at the CPU core level, which usually relates to the use of SIMD instructions, single instruction, multiple data, like AVX family, the SSE family, and so on.
Or it can be a multi-core level. We have multiple core working at the same time and each core maybe having some level of parallelism. Yeah, like GPU level, super multi-core. Do you have a cluster of GPUs that you can use? You need to worry about communication costs and all those things. So there's two basic ways to leverage parallelism. First, to focus on multi-core, because it's simpler. Basically, I guess we'll talk a bit later. One simple way is, let's say you have a message, M, you break it in.
It's maybe long, but you split the computation in two parts. So you have two cores maybe. So you hash like this, you hash like this. And then you merge the results and you get your hash. This is the kind of linear parallelism approach that we applied in some variants of BLAKE 2. Yeah, the P versions, where P is for parallel. Just use multiple cores and each instance was on its own, leveraging the core level SIMD instructions to occupy different arithmetic units.
When we do this, because we saw earlier the computation was this either a sponge or MD construction, it was a chain, right? It was sequential. So how do we get parallelism with these sequential operations? So you would just iterate over blocks in two independent ways. But this is defined by your hash function. It's not something that as an engineer, you have to implement manually. So you call a hash function and under the hood, it will do that. There are some gotchas, some things to get right. The other way parallelism is employed, and which might be more familiar to the people from the ZK space, is using what we often do in computer science when we're stuck.
We use a tree. Okay, I'm going to stop at this level because it grows exponentially. So here you have M1, M2, blah, blah, blah, up to M8. And you end up having the hash here. So the way that is used is not necessarily, oh, my hash function is a tree. I think the function MD6 was designed like that.
And BLAKE's tree design works a bit like that, where you build a binary tree out of your messages, and you have a specific way to pad each of those blocks. It's also used, well, I first got to explain what this is doing. Yes, what's happening in this tree. Because this is not a Merkle tree, right? This is very different. No, it's kind of, so what I wanted to explain was the Merkle tree way, where at each step, so here you use a hash function to hash those two guys together, M1 and M2, which gives you a value here, x whatever. And here again, you use a hash function to hash those two together, you get y.
And then you hash x, y, and you get this value, and then you hash those two, and you get this. So at each node of the tree, you would call a hash function. You would do the padding, you would do the iteration, and you would end up having a value here. So that's what you have, for example, in Bitcoin blocks. Okay, so that is a Merkle tree here. That's a Merkle tree, or a hash tree. So it's binary, it can be ternary, it can be an octotree, like Filecoin is doing. And then again, the choice of the ret and the dev is a performance, simplicity, security consideration.
There's also some things to get right. So I don't want to go too much into theory, but you may have to use different hash functions at different levels to reach some theoretical security levels. For example, this is also used in Sphinx. Which is a signature scheme. Which is a signature scheme, a post-mortem signature scheme that only relies on hash function. So it's essentially building a hyper-tree. A hyper-tree, a tree of trees.
Where the leaves are trees, then everything is a tree. So signing is quite long, because you essentially build a tree. And verification is much simpler, because you essentially go from a leaf to the root. Yeah, like with Merkle trees. Yeah, so another way hash functions are used is hash trees. But we are talking about parallelism. So here, obviously, parallelism. Those two are independent from those two, and so on. So it parallelises in a straightforward way. Then you have to allocate it to your CPUs in a clever way.
So it's both the job of the implementer, the job of the compiler, and the job of the CPU scheduler. It also depends on what you're doing on your CPU. So that's why, so a small commercial pitch here. When we designed BLAKE3, which is the best hash function ever. The best ever, yes. Yeah, this is the best hash function. Remember this thing for this presentation. Seriously, our motivation when we did BLAKE2 first, and then BLAKE3, was that, from my perspective, Keccak SHA-3 was too slow in terms of the number of operations it was doing for its level of security.
You could reach the same level of security with much fewer operations. And the user, I mean, it would be life-changing just for the user. And BLAKE3 is extremely fast because of two reasons. We optimised out the number of runs, so the amount of computation in each permutation. And we optimised also the layout, the tree. So unlike Keccak, we use a tree, which makes it extremely, extremely fast. So faster permutation and tree, which allows for parallelism. Yeah, because at the time when the SHA-3 candidates were designed, there was a bit less interest for multi-core parallelism. And now we have highly parallel systems.
So we mentioned the mode, but we did not talk much about what actually the function is doing, how the CPU is spending its time when you compute a hash. And it will lead us to how to attack hash functions. Okay, so what is actually a computer doing when you hash? So when you compute, for example, BLAKE2 or BLAKE3, it will iterate the following operations. So you're going to transform a vector of three words, A, B, C, and D, where each of those is a 32-bit integer.
So this is happening inside the permutation? Yes, inside the key permutation. Yeah, key permutation, yes. That's a 12. So we do three types of operations here. Addition, integer addition, modulo to the 32, XOR, and word rotation, where you rotate the word by some number of bits. And we repeat this with other offsets.
But essentially, all that BLAKE3 is doing is repeating that over and again, and again, and again, and again. So it looks very, very simple because if you modify, I don't know, one bit, if you modify the most significant bit of this guy, then the most significant bit here will be modified. If you modify one bit here, there will be one change here. So the way we attack or the way we find weaknesses in this kind of algorithm is to track those differences, how the differences propagate. And that's the whole principle of differential attacks. I'm going to illustrate it in a very, very simple way.
Differential means observing differences? Observing differences. So instead of just observing how one message is computed, you observe two messages that are almost the same initially, maybe one or two changes, and you see how the difference propagates. So what you may want to do, that is a strategy that is called the perturb and correct. Perturb, and correct. Also, I mentioned this, but inside VEX3, you do many rounds.
So the round is repeating that over again, and again, and again. And the more rounds you compute, the more differences propagate in a complex way, and the harder it is to track. So just to keep high level, the rounds happen inside the permutation? And then we have this... So you can see this thing like a layer of rounds, round, round, round, round. Which are almost the same. And the more rounds you do, the more secure between speech marks it is. And if you do too few rounds, then you're not going to get these crazy equations I mentioned. So what you try to do, here we have, let's say we have two messages as input, M and M1 and M2. Let's do M and M'.
And M and M', such that M XOR M' are equal to some difference delta. Maybe one or two bit of difference. Sure, so all zeros and one ones? Yeah, for example. And then, we know that statistically, there's a high chance of getting the differential delta one here, as an output of the first compression. So here you give the first message block, we have on the one side M1 and M1' for the other. So I wrote it once, but you compute it twice. You compute it for M1, for M, M and M'.
So we'd also get a delta one prime, I guess? No, because that's the difference between the two. Sorry, sure. And then what you do, so we have all the rounds. You're going to inject a block M2, and for the other one, a block M2', which have some differences. So a difference, I don't know, we call it delta two. So the result between those two. And what you've done, you've done your math, and you know that there's a high chance that if there's a difference delta one here, and difference delta two there, then the difference as an output will be zero. So even a difference in the output is zero, it means that you get what? A collision.
But that was the idea of collision attacks on MD5 and SHA-1. Perturb the state by some well-chosen difference, and find out by analysing those small portions plus zero addition, and see that you have a relatively high chance to get this, and then find a message that leads you to the zero difference. Okay, just a quick question. Here are we drawing our Merkle-Damgård thing again, and would we have this? Oh yeah, simplified by just seeing this as a compression function. But in fact, you have the block cypher, and you have the small thing, yeah. So for MD5, you have this part.
Just so we can keep... So yeah, we could spend hours on that, but just want to give you the general idea of differential attacks. And so if our permutation resists this, if it's hard to find attacks, then we say it's a secure permutation, is that it? Exactly. So there's a few ways to gain confidence that your permutation will be immune to that kind of attack. So the first one is just having many people try for many years to find statistical patterns in it. And when they fail, then maybe it's secure. And then you have slightly more formal arguments by the way you combine and say, oh, if I do two rounds, I get this behaviour. Therefore, I can infer that for this number of rounds,
I will have this level of this metric or this level of linearity. So maybe to conclude, we're going to talk of a different type of hash function. So currently, we talked of bits of exhausts of zeros and ones. Yes. That's easy. How our computer works. Now, what do you do if your computer cannot process zero and ones? It can't really do XOR easily. It cannot do logical AND and logical OR. It can do the plus over the 32-bit integers.
Instead, your computer is a kind of mathematical computers. It works over mathematical structures, like finite fields, like F or K. So we're talking about our circuits or ZKVM. So it can be numbers, modulo some large prime P, which is maybe a long of 31 bits, or long of 381 bits. Or it can be polynomials. And if you want to simulate an XOR using those type of objects, it's really hard and slow and inefficient. So if you want to hash on this type of computer, you're not really going to use maybe BLAKE3 or Keccak. You're going to use something designed for this computer that's
only doing the additions from this world and the multiplications from this world. And when we mentioned that, so that's what is called sometimes algebraic hash functions. And the best known one was... Or SNARK-friendly, right? Yeah. ZK-friendly or SNARK-friendly. Also sometimes useful in other applications. And Poseidon was one of the first, if not the first, that was really designed for this type of computer, but really applying the foundational principles of hash function design. And it works in a very simple way. So first, it's a sponge.
So you only need to design a permutation. The rest is already done for you. Except, I guess, rather than XOR, we do addition? Kind of. It's even simpler than BLAKE3 because I mentioned two things. You need complex equations and you have this idea of difference propagation. So you want to guarantee two things. What is sometimes called confusion or complexity. So the correlation should be very complex, high degree and so on. And the second one is diffusion, which is about having this propagation evenly spread across all the states. So Poseidon does it in a very simple way. So let's say you have four inputs.
You have four inputs. So each of those inputs which will be an element of, for example, your finite field. These are chunks from our message? Typically, they are chunks of the message that you split as blocks. And the first thing it will do, again, you break down the computation as a series of rounds. These are the rounds and then you repeat it. So you do first operation, which is about confusion, which is about introducing high degree. And here we literally do high degree because transformation is just an exponentiation where you multiply each value by itself a number of times. So it can be three or five.
It's not too high because the higher the number, the longer time it takes. But if you only do that, if you only do that, then the output here will only depend on x1, not on those. So what you do, you apply a transformation where instead of doing multiplications, you only do additions. So you're going to get four outputs, but each of those outputs will depend on the four others. So it won't be a simple addition. It will be an addition with coefficients, a combination of those two, of the three, oh, no, what?
Four. A combination of those four with different coefficients in a way that maximises the propagation of differences, but also in a way that is efficient to compute. Is this what we call the round constants? The round constants, oh, they come later. Okay. So here you add constants. Yeah. Why? Because otherwise the round function will be the same at every round. Yeah. And if you only repeat the exact same function, some type of attacks, like slide attacks, could work. So that's why for every round, you do a simple addition with a constant that is different for each round.
Okay. And you repeat that. And then we package 10 rounds together, call that a permutation, and we play the course. It might still be too slow for some ZK proofs. So what is very smart, what the designers did, they're like, okay, this is really solid. If we iterate, it's very, very solid. However, sometimes you want more diffusion than you want that. So some of the rounds are optimised by removing some of the exponential part, which is slower than that. So they would iterate some what they call full rounds with everything here and some partial rounds where you only do one or two of those complex operations.
And I think Poseidon already has passed the test of time. I think as we speak, there's also a competition by the Ethereum Foundation to analyse new versions of Poseidon. And I know some people are a bit nervous because it looks so simple. But from my perspective, I don't really worry about Poseidon. The designers really know what they're doing and there's been no bad surprise in terms of cryptanalysis. So yeah, I think that's pretty much everything I had to say about hashing. We could have many more topics, but is there anything else that we'd like to discuss?
I think that's it. Then this now ties in nicely to everything we've seen in the rest of the whiteboard sessions. That we use hashes for Fiat-Shamir, we use them for Merkle trees. Maybe the last quote, when you use hashes for Fiat-Shamir, it's very important to do two things well, to hash everything that must be hashed in the Fiat-Shamir proof, everything that must be bound to the hash. For example, if you do a Schnorr, then you need to hash over the challenge, the public key, the randomness. And also when you hash those two elements together, it's important to have good domain separation between the different values, particularly if they have a non-fixed size.
Otherwise, you could have collisions by having... Pretending that the sizes are different. Yeah, by having hashing twice the same thing, where it's the same encoding of two different inputs, different sets of inputs. I see. All right. Thank you for that, and I hope that was useful to the audience. Thanks. Thank you.