WEBVTT

00:03.710 --> 00:05.990
Am I stopping the screen?

00:06.570 --> 00:07.530
No.

00:35.730 --> 00:41.890
So we are going to be talking about building a microcontroller Bitcoin address generator.

00:42.550 --> 00:50.970
And what that means is building essentially a homemade hardware wallet, or at least a homemade hardware wallet with some limited functionality.

00:54.490 --> 00:57.710
So first a little bit about myself.

00:57.710 --> 01:01.990
I'm a full-time software engineer at Microsoft based out of Pittsburgh.

01:01.990 --> 01:07.850
So I am working on backend and system stuff for an Azure storage product.

01:07.850 --> 01:17.130
My day job doesn't have anything to do with Bitcoin or blockchain, but I run ChainTuts.com and a YouTube channel on the side.

01:17.130 --> 01:24.810
So I like to create content about explaining how cryptocurrency and blockchain systems work, and what some of the use cases are.

01:24.810 --> 01:28.670
So I put out articles, videos, code projects, and that sort of thing.

01:28.670 --> 01:35.390
And I really focus on teaching the core concepts around blockchain systems and use cases.

01:35.390 --> 01:41.530
I don't really do any investing or money talk, but I do explain why sound money is important.

01:43.490 --> 01:46.250
First, of course, the quick disclaimers.

01:46.250 --> 01:56.960
I am not a cryptography or security expert by profession, but I am a working software engineer and care a lot about code security and those sorts of things.

01:56.960 --> 02:05.980
Security is a complex and evolving topic, especially in the blockchain space when it comes to securing our funds and our wallets and those sorts of things.

02:06.080 --> 02:21.360
So this is really a proof of concept project, and I wanted to show that it was possible and kind of enjoy the process of trying to build an open hardware, open source cryptocurrency tool that I could use and share with others.

02:23.820 --> 02:33.380
So a little bit of context for the project that I actually built here, and that is, what's the core of crypto ownership?

02:33.480 --> 02:39.460
So if you've been around in the space, you know that the core of crypto ownership is private keys.

02:39.740 --> 02:56.900
Every Bitcoin wallet or Ethereum wallet or DigiByte wallet starts out with a seed phrase these days, and that's used to generate private keys, which are 256-bit numbers that are kept totally secret on the client side.

02:56.900 --> 03:00.960
So we never reveal our secret keys with anybody else.

03:01.240 --> 03:05.020
Those private keys are used to generate public keys.

03:05.780 --> 03:12.060
And those public keys are then hashed and encoded and turned into our addresses.

03:12.060 --> 03:19.220
So these are the actual public-facing addresses that we give to somebody else when we want them to send some funds to us.

03:22.570 --> 03:30.090
So private keys are used to prove that we own some amount of Bitcoin funds that we want to spend by using digital signatures.

03:30.450 --> 03:44.130
Anyone with the keys can spend the funds, and so it's absolutely critical when it comes to cryptocurrency that the keys are both securely generated and securely stored so that nobody else can get at them.

03:45.030 --> 03:51.510
Andreas Antonopoulos likes to say, I've heard him say and I repeat often, not your keys, not your coins.

03:51.510 --> 03:56.810
So that's a really critical concept when it comes to any of these cryptocurrencies.

03:57.790 --> 04:07.930
One of the innovations that has come along in the space because of this security concern is hardware and other forms of offline wallets.

04:07.930 --> 04:18.090
So the commercial solutions like the KeepKey, the Trezor, the Ledger, those are examples of hardware wallets.

04:18.090 --> 04:25.270
And what those are is those are specialized devices that generates cryptocurrency private keys.

04:25.610 --> 04:33.390
They store them and generate them securely offline so they don't have to be done on a network device.

04:33.390 --> 04:47.510
And they will sign transactions presented to them using a very limited protocol for communication between the hardware device and the PC that you're using to actually broadcast that transaction to the blockchain.

04:47.610 --> 04:53.790
There are also even simpler and earlier solutions like paper, metal, etc.

04:53.790 --> 04:54.490
wallets.

04:54.490 --> 05:16.250
So people would generate key pairs using maybe an air-gapped network or air-gapped offline computer and sent to a non-networked printer to create a paper wallet and store those key pairs so that they could load up a paper wallet with funds that they wanted to later sweep into an online wallet and spend.

05:18.490 --> 05:28.630
So in general with offline wallets, whether it's a hardware wallet or a paper style wallet, these keys are generated and stored on a device that's not connected to a network.

05:28.630 --> 05:39.810
So this is fundamentally distinct from the kind of hot wallets like full node wallets, like a Bitcoin Core instance, or mobile-like clients like you would use on a cell phone.

05:42.090 --> 05:50.310
Now, so the typical solutions for this, building a hardware wallet to keep your keys secure, is a commercial device.

05:50.310 --> 05:52.990
So these are devices that are developed by a company.

05:52.990 --> 05:56.670
For example, I like the KeepKey, which is developed by Shapeshift.

05:56.810 --> 06:02.130
There's the Trezor and the Ledger, and these are devices that are ready-made that you can go out and buy.

06:02.290 --> 06:13.330
Now, almost all of these devices, as far as I'm aware, do have open source firmware because we often like open source to help mitigate issues of trust and backdoors in the crypto space.

06:13.570 --> 06:19.510
But I kept thinking to myself, why couldn't I build one of these myself on some cheap hardware?

06:19.510 --> 06:30.290
And thus, my project, MicroBitAdder, was born out of lots of tinkering, experimentation, and perhaps getting on my wife's nerves.

06:32.740 --> 06:42.400
So I thought it would be really interesting to generate real offline key pairs for cryptocurrencies using my own code that I developed myself.

06:42.400 --> 07:00.260
I had some basic experience with microcontrollers from another project that I built and presented at the Blockchain Training Conference last year, actually where I met Ron Stoner, and this was a proof-of-work simulator on a little circuit playground device.

07:02.320 --> 07:10.560
So I'm not an embedded engineer by any means, but I had some experience working with these little devices and just found them to be really fascinating.

07:10.560 --> 07:19.880
And I wanted to see if I could build something like this, a tool for generating key pairs, on readily available, fairly straightforward-to-code products.

07:21.160 --> 07:25.220
There's a couple major challenges with this idea, though.

07:25.560 --> 07:35.560
First, I needed a platform with enough memory and computing power to store and run code to do these cryptographic computations.

07:35.560 --> 07:51.720
The M0 platform, which is that little circuit playground device that I showed in the last slide, was way too insufficient for being able to do SHA and ECDSA and store all of that code on the flash memory available on that device.

07:52.820 --> 07:56.720
The next major concern was cryptographic primitives.

07:56.720 --> 08:03.980
So to do Bitcoin address computation, I needed SHA-256, REG-MD-160.

08:03.980 --> 08:08.180
In order to support Ethereum, I needed KEKAC SHA-3.

08:08.180 --> 08:14.880
And I needed the appropriate elliptic curve algorithm used for the public key cryptography.

08:15.240 --> 08:21.740
And the final very important requirement is it has to have a cryptographically secure random number generator.

08:21.740 --> 08:27.420
So I have to have some way to get true randomness, not pseudo-randomness, to generate keys.

08:28.780 --> 08:35.900
So how did I work through these particular challenges in picking a simple hardware platform?

08:35.900 --> 08:41.380
For the memory and power needs, I decided on the Adafruit M4 platforms.

08:41.380 --> 08:52.580
So they have several different devices, such as the Itsy Bitsy M4 and the Grand Central M4, which are the ones that I use to develop and test this particular project.

08:52.580 --> 08:56.320
It runs on an Atmel SAMD-51 processor.

08:57.080 --> 09:05.660
Across the devices, both have at least 256 kilobytes of flash memory and 192 kilobytes or greater of RAM.

09:05.660 --> 09:12.660
So they had plenty of juice to load some cryptographic code and be able to do these computations quickly.

09:15.570 --> 09:18.210
Now, how do I get cryptographic primitives?

09:18.270 --> 09:23.310
I think most of us in this space know kind of the number one rule is don't roll your own crypto.

09:23.310 --> 09:29.810
I'm definitely not a cryptographer and don't have quite the math knowledge to implement my own crypto.

09:29.810 --> 09:38.470
So I needed to get trusted, secure crypto primitives that would run on these little platforms that don't have an operating system.

09:40.230 --> 09:49.050
Standalone Python libraries are fairly hard to find and run on the microcontroller platform due to the space concerns.

09:49.490 --> 09:59.350
On the other hand, what I stumbled on is that the Trezor commercial hardware wallet firmware is open source and very standalone C code.

09:59.350 --> 10:06.550
Most of these individual modules that I needed to run to get this project to work were standalone and didn't tie in other C libraries.

10:06.870 --> 10:15.390
And so what I did was I ended up porting some of Trezor's open source code that was license compatible with what I wanted to release as.

10:15.410 --> 10:19.830
And I'll go into a little more detail on how I did that later on.

10:23.750 --> 10:30.390
Now, the final very important concern was the need for true cryptographically secure random number generation.

10:30.390 --> 10:33.950
And luckily, the platform choice fixed this for me.

10:33.950 --> 10:42.050
The M4 microcontrollers that I used, the entire family of them from Adafruit, come built in with a true random number generator.

10:42.150 --> 10:51.730
If this was a limitation, but the platform was suitable otherwise, there are other options for getting some cryptographically suitable randomness.

10:51.730 --> 10:59.210
I could have done something like use an accelerometer or other device to get the appropriate amount of environmental noise.

10:59.210 --> 11:03.910
But having this built in made life a little bit easier.

11:07.290 --> 11:13.530
So putting it all together, how did I build this standalone address generator project?

11:13.530 --> 11:19.350
The stack starts on the bottom with a custom CircuitPython module written in C.

11:19.350 --> 11:22.750
So I am actually mostly a Python developer.

11:22.750 --> 11:29.690
I do have C and C++ knowledge, but I do a lot of my work at my day job.

11:29.690 --> 11:36.190
And a lot of the stuff I do for chain tutorials in Python because of the ease of development and comfort that I have with it.

11:36.210 --> 11:43.870
It's a very powerful and robust language, even if it's not necessarily powerful in the sense of getting the most speed out of it.

11:43.990 --> 11:52.090
So I wanted to build the application layer for this in CircuitPython, which is designed specifically for these small devices.

11:52.090 --> 12:01.370
And I found that it was possible to extend CircuitPython to build a custom C module where I would do all of the cryptography work.

12:01.450 --> 12:06.590
The device itself is one of these M4 family devices from Adafruit.

12:06.590 --> 12:14.830
So the Grand Central or the Itsy Bitsy with an I2C character LCD screen or a receipt printer hooked up to it.

12:14.830 --> 12:27.590
And ultimately, at the time of doing this presentation, I support Bitcoin, Bitcoin Cash with CashAdder, Ethereum, Litecoin, and Digibyte addresses that you can generate with this.

12:29.190 --> 12:38.470
This was, I think, the most interesting and fun part of this project was building a custom CircuitPython module.

12:38.470 --> 12:46.650
I followed a tutorial called Extending CircuitPython by Dave Astels that's available on the Adafruit Learn website.

12:46.930 --> 12:54.170
And so I went through that tutorial and just learned the basics of creating a custom module for the CircuitPython build.

12:54.270 --> 12:58.700
This is where all the cryptography happens, thanks to the Trezor code.

12:58.700 --> 13:12.820
So I get a true random number generator seed passed in from the Python layer because it's easy to use os.urandom to get your seed from CircuitPython directly.

13:13.820 --> 13:23.540
I then hash that seed into a private key, generate the associated public key, and return an encoded key pair from this layer.

13:23.540 --> 13:26.980
So this is using chain-appropriate address encoding.

13:26.980 --> 13:35.500
It's base58 addresses for Bitcoin, Litecoin, and Digibytes, CashAdder for Bitcoin Cash, and of course hexadecimal for Ethereum.

13:35.760 --> 13:44.280
And as well, this custom module function returns the with or hex-encoded private key, depending on the chain as well.

13:46.750 --> 13:51.810
So this, of course, required actually building the custom CircuitPython module.

13:51.810 --> 14:01.210
I had to run the makefile that's provided by the CircuitPython code, make sure all the files were in the appropriate places.

14:01.210 --> 14:10.750
And this ultimately gives you a UF2 firmware that you can drag onto your microcontroller device and it will reboot with that new firmware.

14:10.750 --> 14:24.150
So since I was actually essentially building the platform that the device runs on from scratch, it required a little bit more work than the basic sort of CircuitPython functionality where you just have to edit a Python file.

14:24.150 --> 14:27.690
This required me getting down into the firmware layer a little bit.

14:27.690 --> 14:40.850
It definitely presented some challenges, but luckily the folks over at Adafruit and the folks that work on CircuitPython are really helpful about getting you up and running if you talk to them on GitHub or somewhere else and have questions.

14:41.910 --> 14:48.810
I personally found, for whatever it's worth, that I was able to get this build running most easily on Linux.

14:48.850 --> 14:57.510
It is possible to build CircuitPython on Windows and Mac, I do believe, but I just worked on Ubuntu when I was working on the core of this project.

15:00.070 --> 15:14.290
Now within this custom CircuitPython module, since all of the Trezor cryptoprimitive code are pretty standalone, it also made it possible to do testing for this on a command line interface.

15:14.290 --> 15:29.590
So every time I wanted to go in and work on some of the important cryptography stuff and make sure that I was getting correct addresses and correct encoding, I didn't have to worry about rebooting the firmware, reloading the firmware, making sure my LCD screen was hooked up correctly,

15:29.590 --> 15:35.890
and all of those little challenges that you run into when you're developing on hardware devices.

15:36.170 --> 15:45.830
So for example, on this slide here is an example of just running a test driver to generate a new Digibyte address right on the command line with a private key.

15:45.830 --> 15:55.210
Since everyone at DEF CON gets to see this key pair, I would say don't take that and send any of your precious Digibyte to it, but I think you all know that.

15:57.590 --> 16:07.330
Now for the CircuitPython layer, all this layer does is it's kind of the application layer that's built on top of the lower level crypto stuff, which is done in C.

16:07.330 --> 16:18.790
This generates the entropy, so I'm using Python's os.urandom function, which is tied right into the CSRNG built into the platform.

16:18.790 --> 16:33.210
I call that custom module code, the function call takes that randomness seed as an argument, and I get a tuple back, so I get the private key and the address back already encoded in the correct format.

16:33.210 --> 16:44.250
And then this code uses the appropriate libraries to display that final address and private key on an LCD screen.

16:44.610 --> 16:49.050
I also tested as well with a receipt printer, which I thought was kind of fun.

16:49.050 --> 17:01.210
You know, it's not the most robust way to store your cryptographic key data long-term, but I could press a button to generate a new Bitcoin address and have it printed right out for me, and I just thought that was pretty cool.

17:01.210 --> 17:11.510
But I primarily tested with an LCD screen, which simply rotates between displaying the address and the private key about every 30 seconds or so.

17:13.970 --> 17:31.350
All you have to do here, and one of the reasons I picked CircuitPython even for an embedded microcontroller project, is with CircuitPython, when you connect the device to your development PC, you'll simply see a drive called CircuitPy mount to that computer,

17:31.350 --> 17:37.610
and you simply edit the main code.py file, and this is where all of that application layer code went.

17:38.010 --> 17:43.870
And when you restart the device, it will automatically run that code.py file.

17:46.030 --> 18:00.470
In this iteration of this project, the usage is really straightforward and simple then, and comes with some pitfalls that I'll talk about next, but each reset of the device will just generate you a new, completely random key pair that you can copy down.

18:02.350 --> 18:20.350
So this is what the final project looks like wired up in a couple different ways, generating Bitcoin addresses with a little itsy-bitsy on the screen with the larger Grand Central, and also showing it generating key pairs and printing to a receipt printer.

18:25.420 --> 18:37.260
So this was a really interesting project, I had a lot of fun building it, and I was really, really pleased with the results and the fact that I was able to get a real working Bitcoin address generator running.

18:37.380 --> 18:50.500
That said, there's a lot of potential pitfalls when you're working with cryptography, and I wanted to talk about some of those issues as part of this talk, because I don't want everybody to think that this is something that you can just take and run with,

18:50.500 --> 18:52.640
that it's guaranteed to be secure.

18:52.640 --> 19:00.360
And more so, what are some of the things that you can run into if you're choosing to tinker with a project like this on your own?

19:00.780 --> 19:06.600
The first and perhaps most obvious pitfalls are cryptography foot guns.

19:06.600 --> 19:12.260
Again, I'm not a crypto expert, and there's always hidden dangers with implementations.

19:12.520 --> 19:25.580
I tried to mitigate those as much as possible by using trusted primitives, so I used libraries for the Trezor hardware device, which is a very, very popular crypto hardware wallet.

19:25.700 --> 19:29.660
The Trezor team works really hard to take in bug reports.

19:29.820 --> 19:43.000
I'm sure has people that are cryptography experts working on the code, and I felt fairly confident using these and was able to, I think, go forward assuming that SHA-256 was going to work correctly if I use their code.

19:43.000 --> 19:45.700
I also kept things simple.

19:46.000 --> 19:51.300
For this at least first round of this project, all it does is generate a key pair.

19:51.300 --> 19:59.940
It generates a random private key and then walks through the steps to get a pub key and finally an encoded usable address.

19:59.940 --> 20:09.640
I don't have any extended support for many of the features that we've come to enjoy, like HD wallets and those sorts of things, and I'll get to that in a bit as well.

20:09.640 --> 20:16.920
And then finally, tested, tested, tested manually against no working implementations of key generators.

20:17.060 --> 20:24.820
So I would use some different places where I would use my device to generate completely throwaway private keys.

20:24.820 --> 20:31.520
I would plug that into a paper wallet tool, for example, and make sure that I kept getting the correct public address.

20:31.520 --> 20:41.540
Because the last thing that I would want to do is send funds to an address and have it turn out that the address was wrong based on the private key and then lose those funds forever.

20:44.440 --> 20:57.220
A major pitfall that many of you may have been thinking of while listening to this right now is that single key pairs are simply way out of favor and considered a legacy way of dealing with private key data.

20:57.220 --> 21:07.960
You have to make sure that if you're generating a single key pair for fund storage, that you copy it correctly and double, triple check before you send any funds to it.

21:08.000 --> 21:16.560
Because Base58, like is used in Bitcoin addresses and in the WIF private key format, is not the most human friendly format.

21:16.560 --> 21:20.920
It's a little bit more compact than HEX, but it's also super easy to screw up.

21:20.920 --> 21:24.280
Was that a capital or a lowercase w that I wanted?

21:24.280 --> 21:29.560
And that was something that I ran into when I was actually testing this stuff with some live funds.

21:29.660 --> 21:34.120
The encoding is just not human friendly and it is easily corrupted.

21:34.180 --> 21:42.320
Bad handwriting, character distinctions like lowercase, uppercase, potential for water damage if you're on paper.

21:42.320 --> 21:47.620
These are all huge problems for long term key storage that are important to consider.

21:47.620 --> 21:52.800
And as is, the code that I generated only shows this key pair once.

21:52.800 --> 21:56.300
So you had to be really careful to copy down everything.

21:56.300 --> 22:04.120
And then if you were going to reset the device, you had no way of verifying the key pair you copied down was correct again.

22:05.440 --> 22:10.460
So let's talk about the fun stuff, which is potential future improvements.

22:10.460 --> 22:18.640
How can we make a project like this that's a tinkerer's homemade offline wallet better, safer, more secure?

22:18.880 --> 22:34.840
I think the first and best thing that I could have done to make this better and would like to consider as I maybe iterate on this project in the future is upgrade to BIP39, BIP44 compatible seed phrases instead of single key pairs.

22:34.840 --> 22:46.980
So instead of directly generating a private key from the true random number generator, generate a seed phrase and encode it as a mnemonic using the BIP39 standard.

22:47.340 --> 22:54.200
This is much, much, much better for reasons that a lot of us in the crypto space are fairly familiar with.

22:54.200 --> 23:06.040
You have a backup that's much more human readable, a backup that is much easier to fix if you do get a little bit of water damage or something like that or dealing with bad handwriting.

23:06.040 --> 23:15.640
It's much easier to figure out what an English word might be if a character or two is off than in a string of seemingly random information like a WIF encoded private key.

23:15.640 --> 23:35.120
We could then generate new addresses as needed or even if we wanted to keep it simple and just grab the first address from the tree, this is still a much safer and easier backup solution even for a single address usage of this type of device.

23:36.980 --> 23:43.880
Now, I will say, of course, again, another thing that's fallen out of favor is address reuse in general for privacy concerns.

23:43.880 --> 23:53.500
So, you know, it would be best if we're using a BIP44 standard in an implementation of this in the future that it would allow you to generate new addresses from the given seed.

23:53.600 --> 24:01.920
But as well, even if it was just used to generate the first key pair, if you have a mnemonic backup, it's going to be much safer for the users.

24:03.260 --> 24:06.440
Several UI UX improvements that could be made.

24:06.920 --> 24:11.080
Warnings about some of the things that I talked about, like, hey, don't lose this.

24:11.080 --> 24:15.900
Make sure that your seed or make sure that your key is copied down correctly.

24:16.460 --> 24:20.720
Maybe, in fact, it should actually store the key pairs for a while.

24:20.720 --> 24:40.440
Since it's designed to be a standalone offline, never connected to a network device, it could be feasible that you would want to keep maybe an encrypted copy of the seed phrase on there or a list of the generated key pairs on there in case the user screws up and wants to go back and make sure they copied something correctly.

24:42.420 --> 24:49.060
This was built on CircuitPython 4, which has now been superseded by CircuitPython 5.

24:49.060 --> 24:56.980
So, it would, of course, be wise to upgrade my custom module, custom firmware to the latest builds for supportability.

24:57.020 --> 24:59.260
Downside, the build process was tricky.

24:59.260 --> 25:03.180
It was probably the hardest part of getting this up and running.

25:03.920 --> 25:07.160
Question, could be a potential improvement.

25:07.360 --> 25:11.060
Is this device powerful enough for a pure Python implementation?

25:11.060 --> 25:25.960
I originally got the idea of doing the lower-level cryptography in C and then having a CircuitPython application layer because I was dealing with the particularly limited devices I was trying to do this on first before I moved to the M4 platform.

25:25.960 --> 25:37.700
I haven't really tested out whether or not the M4 is powerful enough to load pure Python implementations of the cryptography and therefore have a pure Python project.

25:37.700 --> 25:46.380
I would like to do that for easier development and less complexity for both myself and potential users of a project like this.

25:46.380 --> 25:55.520
The downside is, of course, with anything cryptography, making sure that the crypto primitive libraries that I'm using are up to snuff in terms of their security.

25:55.640 --> 26:05.500
And as far as I understand it, there can be some more concerns with doing crypto in a Python environment versus a C environment with side channels and those sorts of things.

26:06.540 --> 26:10.460
I would love to consider doing more unit testing.

26:10.460 --> 26:19.660
It's always a good idea to catch basic cases and edge cases through a unit test instead of manual testing.

26:19.660 --> 26:27.560
So anytime I make some kind of change to the code, I can have test cases that would check and make sure that key pairs are being generated correctly.

26:27.560 --> 26:45.980
A lot of the Bitcoin BIPs that are involved with these sort of wallet generation actions do have some test vectors that I could put into a unit test to make sure that I'm not missing strange edge cases.

26:46.360 --> 26:48.660
And I'm just open to ideas.

26:48.700 --> 26:53.020
I build projects like this outside of work just as something that I enjoy.

26:53.020 --> 26:54.020
I like to tinker.

26:54.020 --> 26:58.000
I like to learn about Bitcoin and crypto tech and then teach other people about it.

26:58.000 --> 27:06.480
So I'm always looking for ideas and perhaps something new will pop into my head tomorrow about how I could work on a project like this and make it better.

27:09.180 --> 27:12.260
And that's all I had for the main presentation.

27:12.260 --> 27:15.140
I would love to take any questions you all have.

27:25.820 --> 27:38.600
If I'm not mistaken, I do believe that the Grand Central M4, which is one of the devices that I worked on, it might have some kind of keystore capability, but I can't say for sure on that.

27:41.500 --> 27:49.280
Yeah, that definitely would be nice and that would be something to investigate as well as part of potential future improvements and tinkering with the idea.

27:51.750 --> 28:04.810
I think at the very least, if I could get it to generate a seed phrase instead of just a raw, you know, with an address key pair, it would certainly make backup safer, even if it doesn't store them on the device.

28:04.810 --> 28:11.270
Because that way, at the very least, you can make sure that you're writing it down correctly and backing it up.

28:11.270 --> 28:14.430
And it's obviously not a full hardware wallet implementation.

28:14.430 --> 28:19.970
It doesn't sign transactions or offer, you know, modern trees of addresses.

28:19.970 --> 28:33.390
It was kind of a proof of concept for the initial idea of can I generate a basic crypto wallet with a little microcontroller, like something that's simpler even than a Raspberry Pi.

28:33.390 --> 28:37.130
And I'm glad I tinkered with it and it worked out.

