 first love story in three acts. So like every good three act play, we will start with a dramatic person a listing of the characters. This is peppercorn. Peppercorn is one of your user's dogs. Like every good dog owner, as soon as they got peppercorn, they went and changed every single one of their passwords on all of their sites to peppercorn. You'll see more of peppercorn later. This is Mallory. Mallory is an attacker who is going to try to compromise us. We'll come back to Mallory as well. And this is me. My name is TJ Shook. I am on the internet everywhere, GitHub, Twitter, et cetera, as TJ Shook, my name without the dots and spaces. I work at Harvest. I'm a developer there. We are online at getharvest.com. We make the world's best time tracking software. If you do anything where you turn your time into money, work for an agency or a consultancy, do freelance work, any kind of thing like that, you should check out Harvest. I have a couple of local points of order first. One is this guy. This is my coworker, Doug Fales. He currently lives in Montana, but he used to live in Boulder. And the tech community here is apparently tight-knit enough that he knows all of you. And so he rattled off like a list of 30 names and told me to meet all of you and say hello, which I promptly forgot. So if you do know, Doug, come say hi to me and I will relay the message. To clarify, I'm talking about the human. I don't know who the fish is. Also, if you know the fish, come talk to me because I'd like to give him credit. Additionally, like everyone, we are hiring. We are hiring in tech roles and non-tech roles. So if you know anyone that's just looking for a job, get harvest.com slash careers. We also have remote people. Two thirds of our company is remote, so that's great. For this talk, however, it is notable that I am not a security expert. I'm not a crypt analyst. I coincidentally have a degree in math, but that doesn't help me here at all. There are people who are actually security experts and crypt analysts, and they have a lot of fancy letters after their names. They get paid a lot of money to know a lot about this stuff that I don't know. If you have true fundamental security issues, you should hire one of them. You shouldn't just listen to this talk and think you're good. But I have users. We heard earlier about how everyone has a users table. By virtue of the fact that I have users, I have to be a security expert because ignorance isn't an excuse. If you are compromised and something goes wrong, you can't say we just didn't know any better. That is not an excuse. And if you are the weakest link in the chain of a greater vulnerability, everyone will come back to you and blame you, and you cannot just push it away as you didn't know any better. So I have to be a security expert by virtue of having users. You do too therefore, so I'm just going to try to help you a little bit there. Back to Mallory. Let's talk about her attack. So good security is about layers. You should have security at all different layers. You should have application level security to protect against things like SQL injection or XSF or CSRF. You should have infrastructure level security. You should have a secure data center with physical firewalls between your devices. You should update your version of Bash. You should do all of these things. However, to analyze any individual layer of the security structure, we have to assume that all of them have failed and that this works. Mallory can just run this and get a database dump of your database because security has failed. So we need to do something to mitigate that threat. So how are we going to authenticate our users? How are we going to store passwords? The easiest option is plain text. Just throw them in the database. You have all your users. They log in with their email. We have their passwords. Great. This is easy, but it's obviously bad and no one here is doing it, right? Right? Anyone want to admit to it? Someone here is doing it and they don't want to admit to it but they're doing it because they have reasons. They are running an internal application so they don't think it matters or they run a website that's for ranking animated GIFs. If their passwords get compromised, it doesn't really matter because people will just come rank GIFs on their user's behalf. But it's bad because users reuse passwords. So if there is a breach of your database, they're going to take peppercorn from your database and then go over to Gmail and try it and go to the banking sites and try it and go to Facebook and try it. And they will get into a lot of those places because users use their dog's name as their password everywhere. So we need some way to obfuscate the data of our passwords because we know that this is bad. So the easiest way to obfuscate it is just to encrypt it. So here are our passwords encrypted. This is a very secure encryption scheme known as ROT13, ROT13, a Caesar cipher with key of 13. You take all the letters and you shift them by 13. So an A becomes an N, an N, a B becomes an O, a C becomes a P, et cetera. That's kind of just for illustrative purposes. It could be anything. It could be DES3, AES256. But the key to all of those things by virtue of being encryption is that they are reversible. You do something to encrypt them in some way, but then you can do something to get them back out again. They have a secret key. In this case, it is 13, but oftentimes it's a more complicated key. But by virtue of there being that secret and by virtue of them being reversible and by virtue of the fact that we have already had our security compromised, the attacker potentially has access to that key as well because it's often stored in app code or on the server as like an environment variable. If they're already into your infrastructure, they can get it. It's also important to note that a malicious actor could just be a malicious employee. If there is a public database dump, but it is still encrypted, if you have a large enough organization, there is likely going to be someone that is nefarious enough to then leak that key in some way. So even having any way to reverse them is bad. So the key here is that encryption is reversible. Hashing is irreversible. So using the correct vocabulary here helps make this a little bit more easy to talk about. Encryption reversible, hashing irreversible. So if you take peppercorn and you hash it, you get some value. If you take secret one, two, three, four and hash it, you get some value. But if you have some value, even as a good actor, not as an attacker, there's no way to go backwards. So that's the first key of hashing. The important other thing is that it's deterministic. So that if you hash peppercorn, you get a value. If you hash peppercorn a second time, you get the same value. If you hash peppercorn a third time, you get the same value. That's important because we're using this for authentication. When users come in with their password and plain text, we need some way to verify it with our stored one. So by rehashing it, we can get the output, make sure they're the same, they're authenticated. It's also important that it's deterministic, but not obvious. So when you hash peppercorn, you get the same value both times. But if you trivially change peppercorn by capitalizing the first letter, you get a completely different output. And also if you have an output that is trivially different right here, the least significant bit is off by one. It's probably completely different from capital peppercorn. So it's deterministic, but not obviously so. So this is now our table with our hash passwords. They are all hashed throughout all of this with MD5, just because it's the shortest, so it fits on slides. But it's the same for SHA-1 and other common hashing algorithms. So this is good. Now we cannot go backwards. So if there is a dump, our passwords are safe. But the problem is what we said earlier that hashing is deterministic. That's a double-edged sword. Because the hash of peppercorn is always the same, the hash of peppercorn is always the same. And that brings us to the concept of rainbow tables. There are three important points of order here. The first one is that this is the best slide that's ever been made. The second important point of order is I've already turned it into a GIF, and I'll tweet it to you later. And the third important point of order is I am not actually going to talk about rainbow tables right now. I'm going to be talking about lookup tables. A rainbow table is kind of just a specialized case of a lookup table. It's just a longer chain than a lookup table, which is a chain of one. But if I was going to talk about lookup tables, my slide would look like this, and that's boring. So instead you get this one, but you have to know I'm talking about lookup tables. So if we have this dump of our database and we have all of these things, we can take the hash and put it into a lookup table. And because that hash is always the same, there are pre-computed values where we can see that that hash backwards is this one. As a proof of concept, we can use the world's best lookup table, which is Google. You can take a hash and just drop it into Google, and you don't even have to leave the results page. And you can just see what that hash is for. So we need some way to render all of these pre-computed tables obsolete. We need some way to defeat the rainbow so that when you go to Google and dump it in, you can't find the output. The easiest way is just to change all the inputs. So we hash peppercorn, we know we get this output, and we know that that's already in these existing tables. So if we just add a string of nonsense, add some entropy, we're now giving everyone secure passwords by default, effectively, because their passwords are kind of random. And we can see that this works because if we look it up in the lookup table, that value has never been hashed before and is now not in Google, so it's probably not in lookup tables, and we did it. We saw password security, we're awesome. So yeah, attackers won't be able to look up passwords in a table, that is true. However, it is trivially easy to generate new tables because they can get that altering scheme from your app code again or from a malicious employee because you're storing that little bit of entropy somewhere, usually in your auth check. On this MacBook Air, which is not usually known as a powerhouse of a machine, I can compute 13 million SHA-1s every second. So if you wanted to make your own table, you can do that very, very quickly. If you had a 13 million word list, take one second to SHA them all and then check them all. If you had a 25 million one, it would take two seconds. So as a proof of concept, this is where harvest was. We had a table of users with a table of SHA-1s passwords that had a global salt applied to them. It was just some nonsense. So before anyone else could do it, I decided to attack us. And I took a dump and I then used a very difficult way to extract all these passwords from there, which is using freely available software that you can download from the internet trivially. There's a program called Hashcat. That's what I use. You can just download it. John the Ripper is another one that you can install via Homebrew. It's not hard to do this. And you need a dictionary, a word list. I got a 25 million word long list in like five minutes of Googling. It's really easy to do these attacks, it turns out. So I took the dump of our data. And just for the sake of any harvest customers here, I did my best to anonymize this data. I only looked at hashes. So though I kind of know a bunch of passwords, I don't know your password. And a lot of them are probably now changed anyway. And right there in the middle was peppercorn, along with 80,000 other passwords from the harvest database that I got in 87 seconds. So in less than a minute and a half, 80,000 passwords, which is not even a majority of our users, but it's definitely enough to do significant damage. I could, if I were a true black hat attacker, go try to log in all those other services and probably get into many of them. Yeah. So it's important to also note that in this crack, I also got these passwords, which seem like they're secure. But these programs and these word lists are really good. People spend a lot of time developing them because they are useful. So that thing like doing the leapspeak alternates of making universe, that's not going to work anymore. The programs are usually smart enough to take the word list and swap out eyes and ones and that kind of thing. The middle one looks pretty secure, too. Until you look down at your keyboard, it's actually, if you look at a QWERTY keyboard layout, it's just like a hardware hack. They're just like marching keys. So there's a lot of those in there, too, because they're pretty common. That last one, I don't even know. I don't know where that's from. It's not some Game of Thrones character that I haven't heard of. But it's just by virtue of these things being well-architected that they can compute shorter passwords, and it's probably just by virtue of the length of it that it got cracked. So we can do one last attempt, which is instead of using this global salt, we can do a per password salt. So instead of hashing every peppercorn in our table with the same global one, we can give every individual one a new random bit of entropy, which we then store alongside of the password hash in our database. Just to know, I got rid of the email column just for space. You would still have that, or else you wouldn't be able to look up your people. Knowing the salt is not particularly useful to the attacker, because that is where the complexity is coming from. We also need some way to look it up. And what this does is it just makes it a computational problem of now instead of having to generate, if you had 1,000 users, one table, you would have to generate 1,000 tables. And if you had 100,000 users, you had to generate 100,000 tables. And so this is actually pretty good. By doing that per password salt, we now have a little bit more computational complexity. It takes longer for us to crack these passwords. It's unfortunately pretty good for 1976. This is roughly what Unix's Crypt 3 does that was written in 1976. And at the time, a computer could calculate approximately four of these hashes per second. But today we have these. This is an AMD AX7990. It costs less than $1,000. So if you really wanted one, you could probably get one, and probably get a couple. It can compute 1.5 billion hashes a second. So MacBook Air, 13 million a second. This 1.5 billion a second. So now we're back to being able to generate these tables so trivially that generating one table per user is actually not a problem anymore. The problem, the reason that's the case is because these hashing algorithms are not designed for password security. They're actually designed typically for file integrity. They're a way to get checksums of things. So you can verify over a network transfer that the thing on one end is the same as the thing on the other end. We are just kind of using it improperly to check that that thing that's the same on both ends is a password. So we need a hashing algorithm that's not designed to be fast like these are so that your network transfers are faster. We need one that's designed to purposefully be slow. So in 1999, Niels Provost and David Matziaris published a paper about future adaptable password schemes where they came up with Bcrypt. Now Bcrypt hits all the sweet spots that we've already talked about. It's a one-way hash. It's pre-image resistant. It's deterministic. It has built-in per-password salts. We'll see that in a second. The salts are 128 bits long. That's a nice bit of extra entropy. And there are two additional goodies. One is the underlying cipher, which is X blowfish. It's based on blowfish, which is a notably expensive cipher. But the EKS stands for Expensive Key Schedule. So it's even more expensive to get the algorithm up and running. It takes longer to get booted. And it takes more memory. By virtue of requiring more RAM, it kind of mitigates the problem of GPUs, which are typically just processing power. They don't usually have RAM along with them. But more importantly and more interestingly is the notion of an adaptive cost. So let's go back to our database dump and look at it again. These are now the passwords hashed with Bcrypt. And let's investigate a Bcrypt digest. So ignore the dollar signs. They're just delimiters. They don't mean anything. This last column, I guess you would call it of the digest, is the actual hash. That's the check sum that comes out at the end. It's a 192-bit number encoded in a slightly modified base 64. And then to the left of it, that's the sult. And instead of being stored in a separate database column, it's stored right in the digest. So it's more convenient. And we don't have to worry about digest. They're built into the algorithm. I'm sorry, the sultz. On the far end there, it's just an identifier of the algorithm we've used so that this actually came out of Vudix's password schemes where you could have multiple different ones used for different users and also change them over time. 2a just means this is Bcrypt. There's also 2x and 2y, which also mean this is Bcrypt for historical reasons. We can talk about them later. They're not that interesting. But more interesting is the second column, that 10. And that's the cost. So what does that cost mean? It means that when we hash peppercorn with Bcrypt with a cost of 10, we get some output. When we hash peppercorn again with a cost of 10, we get a different output. But we already expected that because of salting. So no big deal. When we hash it a third time with a cost of 14, we get a third output. This is not that interesting until you look at how long it took for each of these to happen. So the first one took about 0.06 seconds. The second one took also about 0.06 seconds. But the third one took more than a second. So what we can do is we can march forward this cost over time as computers get faster. So if we start today with a cost of 12, a year from now, we can use a cost of 13. And 10 years from now, we can use a cost of 25. And we will keep up with the computing speed. These are the approximate average times it would take to do them on my computer using each of these costs. And the thing that you kind of have to balance is the security of being able to have a long cost and the login of your users. So if you are just doing a login flow, if you wanted to increase that by four seconds, that's kind of bad. But if you increase it by a half a second, it might not be noticeable. But if you use basic authentication for your APIs where every single thing comes with a password, then it's a half a second added to every request. You might not want that. So you do have to kind of balance it for your use case. But what is good is that as computers get faster, you can increase it. And that's that future adaptability. So using Bcrypt with our cost that we have set, the attack that took 87 seconds before, a minute and a half, would now take about 84,000 years to complete. So that's a pretty good improvement. Bcrypt is kind of the sweet spot for us because it has been researched for many years. It was written by true crypt analysts. It's secure. And it has a Ruby library, which we'll get to shortly. Some of you are now saying in your head, well, why don't you just use PBKDF2? Or why don't you use Script? Or why don't you use some other password hashing algorithm? And that's fine, too. You're probably OK. PBKDF2 is actually the one that I think is the government-approved kind of password hashing thing. So if you do any government work, I think you have to use that. They are both fine. I think they're worse, but they're not significantly worse. If you are already using one of them, you don't have to convert over. But you're already ahead of the class. So don't worry about it. So how can we fix our problem? Now that we have all of these shod passwords, we need some way to convert them to Bcrypt. If we already have plain text, if you're already in that first camp, it's easy. You can just kind of run a one-shot to do it for you in the background. But otherwise, we need the plain text password at some point. If we have the hash because it's irreversible, we can't then encrypt it with Bcrypt. So the only way we can do it is the only time that we get the plain text password, which is by hooking into our authentication. So this is just effectively a stubbed-out method of authentication. Password comes in, you use your hashing method, and you check to see if the digest matches the hashed password coming in. We want to get to here using Bcrypt to do it. The astute readers of the audience will notice the double equals up here that is seemingly comparing the plain text password to the Bcrypt digest when it's going into a method. And some of you are saying, I thought Bcrypt was irreversible because it's a hash. It is. Bcrypt Ruby overrides the equals equals operator to make this a compare. I think this is one of the worst design decisions of Bcrypt Ruby, but you will see it happen. And if you do this in poll review, someone will point it out. So just so you know that's what's happening. There is an alias for it that I think is called isPassword that you can use, but that's a little awkward too. I keep meaning to change that. But that's what that is there for. So the easiest way to get here is just kind of like throw in a pre-filter to our off-flow to convert, where we just, you know, if we have already converted, or yeah, we just go through the conversion and then we do the Bcrypt one. And that conversion is we return if we've already converted. Bcrypt has a nice valid hash method there, so you can check if it's already done. Otherwise, we have the plain text, just update it in place to the Bcrypt one. And you can do it in the background and that's exactly what we did. All of this code is actually taken right out of harvest when we did the conversion. And these were our users with Bcrypt passwords over time. And you can see there was a big spike initially as all the daily users and the Mac app authenticated and all those things. And then it kind of slowly trailed off over time. But it got us most of the way there. We still had some remaining ones. That taper, though, was unfortunate. But luckily, I had already gotten a bunch of plain text passwords when I attacked the database. So I just did it a second time, but this time had it convert all the passwords. And that did get us the rest of the way there. There were a couple of outliers that actually had good passwords that we couldn't crack or that did not log in in the couple of weeks that we did this. For them, we just reset their password and sent them an email and told them they had to change their password and we got no complaints. So it's not even that scary to do, like people won't freak out. There is one downside to Bcrypt. Because it's an expensive algorithm, that means it's an expensive algorithm. So you will see your load increase, particularly we do support basic auth from our API. So any API call that's coming in with a password has to go through the Bcrypt overhead. So we saw actually roughly a doubling of our CPU load, but it's definitely within the realm of acceptability and the trade-off for the security that we got from it was definitely worth it. So just be aware of it, but it's not that bad. So act one was just the exposition. That's the boring part. You can learn all that on your own. Act two is where you add conflict in a three act play. So we're going to talk about that binary gems. So as I mentioned earlier, Bcrypt has a Ruby library called Bcrypt Ruby. You can find it on GitHub at Code of Hail, Bcrypt Ruby. There's a Ruby gem, great. While doing this, I wanted to add a feature to Bcrypt Ruby. But when I tried to do that, the test didn't run and the dependencies were out of date and they were missing docs. So my one pull request turned into a dozen pull requests. And then if you do that long enough, eventually someone will just say, can you please give this person commit bit? And then suddenly I was the maintainer of Bcrypt Ruby. There have been sort of three de facto maintainers. Code of Hail is the third one. He wrote it initially. And then Tenderlove, Aaron took over. And then TMM1, Amangupta took over from GitHub. And now it's me. That number of 5.8 million is now like 12 million downloads. That's terrifying, but so it goes. Anyway, this is what Bcrypt Ruby looks like. But more accurately, this is what Bcrypt Ruby looks like. Bcrypt Ruby is just a wrapper around C and Java extensions. So the reason that's true and the reason that it's not natively implemented in Ruby, which it could be, is Ruby is, I don't wanna say slow because that has been debunked and argued ad nauseam. But it's definitely slower. And your attacker is going to use the fastest thing they have. Your attacker is going to use a C implementation. So you don't want to have unnecessary overhead and then lower your cost to make it more speed efficient when your attacker is then going to have the advantage. So it uses a C and Java extension. What that means though is when you distribute a gem, you have to distribute it with compiled binaries for the sake of your users so that they're not required to have a compiler on their system or the JDK or anything like that. So you can see the 3.15 actually has four versions there. The third one's a native one. The fourth one is the Java version. And then those top two are Windows fat binaries. What they are is they're gems that are built with support for multiple versions of Ruby because that's how Windows plays. However, I'm not a Windows developer so I don't really know much about doing that. Luckily, when Amon added this to be CryptRuby, he wrote this awesome commit message with all of these instructions. But he wrote it three years ago and like anything on the internet written about computers that's three years old, it doesn't work. Aaron on his blog actually introduced the notion of fat binary gems five years ago where he made the same queen joke as me five years before me but also anything written about computers on the internet that's five years old definitely doesn't work so none of that works. Ultimately, all of this though is just wrapping up break compiler which is this library that uses the power of rake to help you cross compile your gems so that you can provide these binaries to all of these different platforms. Nice long documentation with all of these instructions and none of that works. So, the Rails team has this thing which I think is great. It's called the Rails DevBox and what it is is it's a virtual machine that has in the box all of these external dependencies so that you are not required to have them on your local machine, things like all of the databases and the system dependencies for running all of them. So that's very useful so you can run the tests on your local machine without having every possible environment set up there. I had a dream that I wanted to create a rake compiler dev box. I would do the same thing. In that dev box there'd be all the rubies I need, the GCC, JDK, MingW which is the library for compiling Windows binaries on Knicks-like machines and Vagrant exists and like configurable lightweight reproducible portable developer environments. It's exactly what I wanted. So I set up a Vagrant box that had everything I needed to compile these binaries and none of that worked. So like anything that doesn't work you put it on GitHub. And with that I opened up rake compiler issue number 79. This is where I effectively said on rake compiler I followed all of the docs as long as possible I tried to do it in like a cleanest environment as possible nothing worked, please help me. In our three act play this would be the climax. This is the turning point. And you were promised a love story. This is Luis Levena. Luis is the developer of the one-click Ruby installer for Windows. By virtue of working on all of the stuff for Ruby on Windows, he is also a member of the Ruby core team. By virtue of both of those things he was voted a Ruby hero in 2010. But more importantly to all of us here today he's the developer of rake compiler. And after I opened up that issue on rake compiler he opened up rake compiler dev box issue, our poll request number two where in an epic poll request that's just great and I highly recommend reading he dropped on me triple hearts. Not once, not twice, not three times, but four times, but here's the problem. Like this was a great poll request and everything but I'm 12 hearts in the hole. So I need you guys to help me and pull out your internet devices and tweet at Luis right now three hearts. Additionally, if you are on a Mac on Mavericks or later if you hit control command space you get all the fancy emoji. So go to town on Luis, I'll wait. Thank him for being a great OSS contributor and maintainer. He really is just the best. While you're doing that to half of you I want to encourage you to find your Luis. Thank the person that has helped you to do the things that are often too hard for you to figure out on your own but more importantly work with them because thanking is easy but actually collaborating is a lot of fun and very rewarding. Luis lives in Argentina and Paris. He splits his time between them. So it was a nice like global collaboration to the other half of you that are already maintaining giant libraries or are already a mentor of some way. I want to encourage you to be the Luis that you wish to see in the world. Because, well, don't laugh. It's easy to, you know, when someone comes in with the same issue that you've seen a hundred times to just say like RTFM, like just go figure it out but consider that the problem might be the FM and rewrite your docs to be more friendly to people or just be supportive of them when they open up usage issues which I know a lot of people don't like GitHub for usage support but you can be friendly to people. So what have we learned today? Number one, just use Bcrypt. If you're not already doing it, you should do it. Come find me afterwards. We'll cry, we'll hug, we'll convert our passwords. Number two, distribute a dev box. If you have any kind of complicated library that you're shipping, make it easier on other people to run it by distributing a dev box. Alternatively, if you make or maintain a gem that has extensions, try a compiler dev box. It'll make your life a lot easier. But most importantly, I want to encourage you to release, to collaborate, and iterate. With that, I'm TJ Show, thank you. I have 52 seconds for questions. How far will that adaptive cost value scale was the question? Potentially, endlessly, it's just an integer that you pass in. So yeah, like as time marches on, you could raise that cost to a hundred or a thousand. Something tells me that will no longer be our problem. So in our lifetimes, we might hit 20. But even still, it's increasing that cost factor, increases it by a power of two. So even upping it by one does do a fairly decent amount. It's the power of two also, like Moore's Law Computers are also keeping up with that. But I think we have some time on it. Anyone else, anything? Yes. Did we have any min or max values on the password field in our database? Yes, now. But Harvest is actually a decently old Rails app. It was written on pre-Rails 1. So I think Harvest launched in 2006, and we were all terrible at the internet. So I think our password restrictions were much looser then. And so as time has gone on, they've gotten stricter. But legacy users have had worse passwords. So today, if you have a Harvest account, yeah, you have a more secure password. It's required to be longer. It's required to have a little bit more complexity to it. But as we saw, that doesn't necessarily help. So I also just encourage you as users to use something like a password manager, like one password or last pass or something. But yeah, so now we have stricter requirements. But just over time, there have been looser ones. What the passwords were that I couldn't crack? By definition, no. But by looking at that list, there was nothing that was like a 30-character long random string. There were some passwords that were like pass phrases, like the XKCD comic that you've seen a lot. That XKCD comic is partly true in a strictly computational sense. But again, these word lists have gotten better. And a lot more of them now are in the tools have to where the tools will now, if you're willing to wait longer, start combining words in different orders. So I do truly recommend actually random passwords. But length is also good. But yeah, so nothing that shouldn't have been there. Yes. So the question was about devise. A lot of people use devise. Devise uses Bcrypt. I believe devise uses default cost of 10, which is actually the default cost of the Bcrypt Ruby library currently. That might be a little low. I think we're running at 12. But again, it depends on your use case. You can easily set the cost externally, though. So that devise will just use that cost under the hood. Additionally, related to devise has secure password, which is built into Rails Active Support, which is kind of like a minimal implementation of doing this. Also uses Bcrypt. So if you use either has secure password or devise, you're set. Yes. All right. The question was, do I know anyone that's implementing a gem to do two factor? I don't offhand. I wouldn't be surprised if there's one. But no, I do not know. All right. If you do have a question, but we're too shy to ask, you can find me afterwards. I'll be around. Thank you.