How to Sign and Verify NFC Data With React Native and elliptic
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo set up the project on your local machine, please follow the directions provided in the README.md
file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.
This lesson preview is part of the The newline Guide to NFCs with React Native course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to The newline Guide to NFCs with React Native, plus 70+ \newline books, guides and courses with the \newline Pro subscription.
[00:00 - 00:17] Hello, in this lesson we will focus on how to create and verify the digital signatures to represent our NFC Pokemon. We will do a quick review of the asymmetric cryptography.
[00:18 - 00:28] Then, we set up our cryptography library for React Native. We will use the popular elliptic npm package.
[00:29 - 00:47] However, because the elliptic package is a normal node.js library, rather than a React Native specific one, we will have some extra work to do. Third, implement the signature creation and verification process.
[00:48 - 00:53] Let's get started. Let's first take a look at this diagram.
[00:54 - 01:14] As you can see in the diagram, Alice wants to receive messages from Bob, and she wants the message to be private. It will be easy if Alice and Bob can simply share a common key for both encryption and decryption.
[01:15 - 01:22] But that means Bob can be trusted. What can we do if this is not the case?
[01:23 - 01:37] To handle this case, we can use asymmetric cryptography. In the current diagram, Alice produces a key pair, private key and a public key.
[01:38 - 02:00] Alice keeps the private key and gives the public key to Bob, or anyone who wants to send message to her. Bob can now encrypt his message to Alice with this public key, and only Alice who has the private key can decrypt the message.
[02:01 - 02:14] Besides encryption and decryption, there is another major use case for asymmetric cryptography. Let's take a look at this diagram at the right side.
[02:15 - 02:29] This time, Alice wants to send a public message by public. We mean that the message is not encrypted, and anyone can see and understand it.
[02:30 - 02:38] However, she wants people to be able to verify that. This message is indeed sent by her.
[02:39 - 03:02] So Alice uses her private key to sign this message and deliver the message together with this signature. Now, if there is anyone who wants to verify whether the message is created by Alice, they can use the public key to achieve that.
[03:03 - 03:13] That's exactly what we like to do in the rest of this lesson. We want our NFC Pokemon tags to be verified by anyone.
[03:14 - 03:29] But only we, the author of this app, can create or modify any NFC Pokemon tags. To create the signature, we will use two pieces of information.
[03:30 - 03:40] The first one is the Pokemon data, encapsulated in the NFC tag. The second one is the NFC UID.
[03:41 - 04:10] Recall from previous lessons that the UID for an NFC tag is a unique hardware identifier, which is normally fixed during the NFC tag manufacturing process. By putting the UID into our signature, we can prevent the issue of people create fake tags by simply cloning the whole tag content, because every NFC tag has a different UID.
[04:11 - 04:24] This kind of fraudulent tag won't pass our signature verification. For the cryptography library, we'd like to use the well-known elliptic npm package.
[04:25 - 04:32] However, we notice one thing. This package is not a real native specific library.
[04:33 - 04:48] What's the difference, you might ask? Well, the note says built-in API, such as buffer, events, process stream, and so on, are not available on the real native platform by default.
[04:49 - 05:04] So, we need to fix this issue in order to use the elliptic package. There is another npm package called our nnotify, which can help us deal with this situation.
[05:05 - 05:18] As the name suggests, the Rnnotify helps us to bring note says API into our real native world. Let's set up our project now.
[05:19 - 05:35] First, install both Rnnotify as well as elliptic. The Rnnotify is actually a death dependency, so you should pass the -d flag when you installed it.
[05:36 - 05:47] Once the installation is complete, add the following two commands into your npm scripts. Nodify and post install.
[05:48 - 06:11] The nodify command will instruct our nnotify about the note says API you'd like to add. So, it can install them automatically for you and perform required modifications to make these APIs available on the real native platform.
[06:12 - 06:25] The post install command will instruct npm to run nnotify automatically after npm install. So, we don't need to run it manually every time.
[06:26 - 06:41] Next, we can do npm run nnotify to invoke nnotify script from the very first time. After that, we can see our project has been modified.
[06:42 - 07:02] As you can see, Rnnotify installs several required packages for us automatically. Besides that, it also creates a shim.js for us.
[07:03 - 07:21] To help us polyfill several required global symbols, such as process or buffer. The last step is to include the generated shim.js file into our index.js.
[07:22 - 07:37] Now, our native project should be able to play well with the elliptic library. Let's move on to implement our signer by using the elliptic library.
[07:38 - 07:49] Open the file, srcutius, signer.js. First, import some utility functions from hexutius.
[07:50 - 08:12] This file comes with several simple utilities to perform the format conversion between byte array, hex string, and normal ASCII code string. Next, create a new elliptic curve with srcp256k1 configuration.
[08:13 - 08:30] By the way, the srcp256k1 curve is also used in many blockchain technologies, such as Bitcoin and Ethereum. After that, let's create our key pair.
[08:31 - 09:03] The elliptic curve object, ec has a key from private function to accomplish this. The return key pair object has a side function to create signatures. Please beware that normally we'd like our private key to be as random as possible, so using a predefined string, like our example here, is not a best practice.
[09:04 - 09:16] However, it should be fine for a demo app like this. We also create a public key key pair object from the previous private key object.
[09:17 - 09:33] This one has a verified function for verifying signatures. With this setup, it is very easy to implement our sign and verify functions.
[09:34 - 10:07] For the sign function, the input we expected is a message in hex string format, called msghex. In the function body, we first call key pair.sign to create our signature.
[10:08 - 10:23] For the output, we also expect it to be hex string, so we perform some conversions here. The verified function is even simpler.
[10:24 - 10:51] We expect both the input parameters to be hex strings. For the implementation, we simply call public key.verify and return the result, which is a boolean to indicate whether the verification is passed or not.
[10:52 - 11:09] Let's write a simple test case to make sure our signer can do its job. Open signature.js under our test directory.
[11:10 - 11:31] First, import both hex UTOs and signer. In our test function body, create a message in hex string and pass it into signer.sign.
[11:32 - 11:57] Let's also print out the signature to see what it looks like. For the expect statement, we simply call signer.verify and pass the signature to it and expect the return value to be true.
[11:58 - 12:10] Run the test case. It passed.
[12:11 - 12:23] Let's implement the write signature function. Import NFC Manager, hex UTOs and signer.
[12:24 - 12:54] In the function body, our first step is to retrieve the NFC UID by calling nfcmanager.get tag. This API responds with a tag object, which has a hex string id property to represent the tag UID.
[12:55 - 13:09] Then we combine pokemon bytes and nfc UID to form the message hex. Remember, our signer expects hex string as input.
[13:10 - 13:22] So we perform a conversion here. Then call signer.sign to create our signature based on pokemon data and UID.
[13:23 - 13:42] Before actually writing to tags, we convert the hex string back to bytes since we can only write bytes into our NFC tags. Next, it's time to actually write them to NFC tags.
[13:43 - 14:02] Recall from previous lessons that the basic writing unit for type 2 tag is per page or per block. Each page or block has 4 bytes and our starting page index is 12.
[14:03 - 14:36] Because our signature has 64 bytes, so let's write loop and consume 4 bytes in every execution. To send commands into type 2 tags, we can call nfcmanager.mca handler.transcif.
[14:37 - 15:12] The write command always starts with a a2 in hex, followed by the page index and the data you'd like to write. Finally, we can implement our verify signature function.
[15:13 - 15:46] Again, we need to import nfcmanagerhexutil as well as signer. Inside the function body, we first construct our msghex by combining pokemon bytes and nfcuid, just like what we did in the previous write signature function.
[15:47 - 15:59] Then, let's pull out our signature from the NFC tag. Our signature data starts from page index 12.
[16:00 - 16:31] Recall from the previous lesson that the read unit for type 2 tag is 4 pages or 16 bytes in total. Since the signature is 64 bytes long, we need to perform the read operation 4 times.
[16:32 - 17:10] To verify the signature, we simply convert the signature data to a hex string and pass it to signer.verify. Just beware that a 64 byte data can become a hex string with length 128.
[17:11 - 17:24] Awesome! Now we can both create and verify the signature using nfc tags. Thanks!