Cryptographic Co-Processor ATECC508A (Qwiic) Hookup Guide

Pages
Contributors: QCPete
Favorited Favorite 6

Arduino Library

Note: This code/library has been written and tested on Arduino IDE version 1.8.10. Otherwise, make sure you are using the latest stable version of the Arduino IDE on your desktop.

If this is your first time using Arduino, please review our tutorial on installing the Arduino IDE. If you have not previously installed an Arduino library, please check out our installation guide.

We've written a library to easily get setup and start using the Cryptographic Co-processor ATECC508A. If you'd like to jump right into signing and verifying, then feel free to skip ahead to Example 1: Configuration However, it's not a bad idea to familiarize yourself a bit with the source code behind the examples, so let's take a closer look at the available functions in the library. You can install this library through the Arduino Library Manager. Search for SparkFun_Cryptographic and you should be able to install the latest version. If you prefer manually downloading the libraries from the GitHub repository, you can grab them here:

Let's get started by looking at the functions that set up the ATECC508A.

Setup

.begin() or .begin(i2caddr, TwoWire)

Creates a connection to an I2C device over the I2C bus using the default or specified I2C address.

Input: i2caddr

Unassigned 7-bit integer for device address. If not defined, the library will use the default I2C address stored in the I2C library (0x60). This can be changed by writing the I2C address to the specific Configuration Zone address (byte 16) prior to locking that zone.

Input: Wire

Wire port assignment. If not defined, the library will use the default Wire port. (*This is only available to change on boards that in fact have multiple Wire Ports (i.e. Artemis). Currently, only the default wire port located on the Qwiic connector is supported on the Artemis.)

Output: Boolean

True- Connected to I2C device on the default (or specified) address.
False- No device found or connected.

Communication

.wakeUp()

Checks to see if a device over the I2C bus is connected. Returns boolean true or false depending on if the slave device has correctly ACK'd to an I2C request and responded with the correct "wake verification" response: 0x11.

Output: Boolean

True- Device present on the default (or specified) address.
False- No device found or transmission error.

.idleMode()

The ATECCX08A goes into the idle mode and ignores all subsequent I/O transitions until the next wake flag. The contents of TempKey and RNG Seed registers are retained. (*Idle Power Supply Current: 800uA.)

Note, it will automatically go into sleep mode after watchdog timer has been reached (1.3-1.7sec).

.receiveResponseData(uint8_t length, boolean debug)

This function receives messages from the ATECCX08a IC (up to 128 Bytes). It will return true if it receives the correct amount of data and good CRCs. What we hear back from the IC is always formatted with the following series of bytes:

COUNT, DATA, CRC[0], CRC[1]

Note, the count number includes itself, the num of data bytes, and the two CRC bytes in the total, so a simple response message from the IC that indicates that it heard the wake condition properly is like so:

EXAMPLE Wake success response: 0x04, 0x11, 0x33, 0x44

Input: length

Unassigned 8-bit integer.
Length of data in bytes (max 32 bytes).
Includes: Count + DATA + 2 CRC bytes.

Output: Boolean

True- Communication complete and message count and CRC are good.
False- Communication failure (count error, CRC error, or no device present)

.checkCount(boolean debug)

This function checks that the count byte received in the most recent message equals countGlobal. Users must call .receiveResponseData and then immediately call this to check the count of the complete message.

Output: Boolean

True- count received and count calculated match (aka inputBuffer[0] == countGlobal)
False- Count error, message invalid.

.checkCrc(boolean debug)

This function checks that the CRC bytes received in the most recent message equals calculated CRCs. Users must call .receiveResponseData then call immediately call this to check the CRCs of the complete message.

Output: Boolean

True- CRC received and CRC calculated match (aka inputBuffer[0] == countGlobal)
False- CRC error, message invalid.

.atca_calculate_crc(uint8_t length, uint8_t *data)

This function calculates CRC. It was copied directly from the App Note provided from Microchip (*it seems to be their own unique implementation of CRC cacluation.).

You're CRC bytes will be located for use at global public variable .crc.

Input: length

Unassigned 8-bit integer. Length number of bytes in buffer.

Input: data

Unassigned 8-bit integer. Data to be used in the CRC calculation.

.sendCommand(uint8_t command_opcode, uint8_t param1, uint16_t param2, uint8_t *data, size_t length_of_data)

Generic function for sending commands to the IC.
This function handles creating the "total transmission" to the IC.
This contains WORD_ADDRESS_VALUE, COUNT, OPCODE, PARAM1, PARAM2, DATA (optional), and CRCs.
Note, it always calls the wake() function, assuming that you have let the IC fall asleep (default 1.7 sec)
Note, for anything other than a command (reset, sleep and idle), you need a different "Word Address Value",
So those specific transmissions are handled in unique functions.
Input: command_opcode

Unassigned 8-bit integer.
COMMAND_OPCODE_INFO - Return device state information.
COMMAND_OPCODE_LOCK - Lock configuration and/or Data and OTP zones.
COMMAND_OPCODE_RANDOM - Create and return a random number (32 bytes of data).
COMMAND_OPCODE_READ - Return data at a specific zone and address.
COMMAND_OPCODE_WRITE - Return data at a specific zone and address.
COMMAND_OPCODE_GENKEY - Create a public and/or private key and store it in a slot.
COMMAND_OPCODE_NONCE - Generates a nonce for use by a subsequent command.
COMMAND_OPCODE_SIGN - Create ECC signature with TempKey and designated private key.
COMMAND_OPCODE_VERIFY - Verify an ECDSA (R,S) signature against data and public key.

Input: param1

Unassigned 8-bit integer. Unique to each command.

Input: param2

Unassigned 16-bit integer. Unique to each command.

Input: data

Unassigned 8-bit integer pointer. Unique to each command.

Input: length_of_data

Unassigned 8-bit integer. Unique to each command.

.read(uint8_t zone, uint16_t address, uint8_t length, boolean debug)

Reads data from the IC at a specific zone and address.
Your data response will be available at .inputBuffer.
Input: zone

Unassigned 8-bit integer.
Zone to be read from.
ZONE_CONFIG - Configuration Zone.
ZONE_OTP - One Time Programming Zone.
ZONE_DATA - Data Zone (for keys and certificates).

Input: address

Unassigned 16-bit integer.
Address within zone be read (must include modification for offset)

Input: length

Unassigned 8-bit integer.
Length of data to be read (can only be 4 bytes or 32 bytes)

Output: Boolean

True- Communication complete and message count and CRC are good.
False- Communication failure or command execution failure.

.write(uint8_t zone, uint16_t address, uint8_t *data, uint8_t length_of_data)

Writes data the IC at a specific zone and address.
Note, once a zone is locked, you can no longer write to it, and this function will return false.
Input: zone

Unassigned 8-bit integer.
Zone to be read from.
ZONE_CONFIG - Configuration Zone.
ZONE_OTP - One Time Programming Zone.
ZONE_DATA - Data Zone (for keys and certificates).

Input: address

Unassigned 16-bit integer.
Address within zone be written to (must include modification for offset)

Input: data

Unassigned 8-bit integer pointer.

Input: length_of_data

Unassigned 8-bit integer.
Length of data to be written (can only be 4 bytes or 32 bytes)

Output: Boolean

True- Communication complete and message count and CRC are good.
False- Communication failure or command execution failure.

Configuration

.writeConfigSparkFun()

Writes the necessary configuration settings to the IC in order to work with the SparkFun Arduino Library examples.
For key slots 0 and 1, this enables ECC private key pairs, public key generation, and external signature verifications.
Output: Boolean

True- All write commands were successful.
False- Communication failure or IC command execution failure.

.lockConfig()

This function sends the LOCK Command with the configuration zone parameter and listens for a successful response (0x00).
Output: Boolean

True- Lock command was successful.
False- Communication failure or IC command execution failure.

.lockDataAndOTP()

This function sends the LOCK Command with the Data and OTP (one-time-programming) zone parameter and listens for a successful response (0x00).
Output: Boolean

True- Lock command was successful.
False- Communication failure or IC command execution failure.

.lockDataSlot0()

This function sends the LOCK Command with the Slot 0 zone parameter and listens for a successful response (0x00).
Output: Boolean

True- Lock command was successful.
False- Communication failure or IC command execution failure.

.lock(uint8_t zone)

This function sends the LOCK Command using the argument zone as parameter 1 and listens for a successful response (0x00).
Input: zone

Unassigned 8-bit integer.
LOCK_MODE_ZONE_CONFIG - Lock only the configuration data zone.
LOCK_MODE_ZONE_DATA_AND_OTP - Lock only the Data and OTP zones.
LOCK_MODE_SLOT0 - Lock only the KEYID data slot 0.

Output: Boolean

True- Lock command was successful.
False- Communication failure or IC command execution failure.

.readConfigZone()

This function reads the entire configuration zone EEPROM memory on the device.
It stores them for viewing in a large global public variable array called .configZone (128 bytes).
In addition to configuration settings, the configuration memory on the IC also
contains the serial number, revision number, lock statuses, and much more.
This function also updates global variables for these other things.

.createNewKeyPair(uint16_t slot)

This function sends the command to create a new key pair (private AND public)
in the slot designated by argument slot (default slot 0).
Sparkfun Default Configuration Sketch calls this, and then locks the data/otp zones and slot 0.
Input: slot

Unassigned 8-bit integer. The KEYID data slot you'd like it to be created in.

Output: Boolean

True- GENKEY command was successful.
False- Communication failure or IC command execution failure.

Sign and Verify

.createSignature(uint8_t *data, uint16_t slot)

Creates a 64-byte ECC signature on 32 bytes of data.
Defauts to use private key located in slot 0.
Your signature will be available at global public variable .signature
Note, the IC actually needs you to store your data in a temporary memory location
called TempKey. This function first loads TempKey, and then signs TempKey. Then it
receives the signature and copies it to signature.
Input: data

Unassigned 8-bit integer pointer. (Max 32 bytes).

Input: slot

Unassigned 8-bit integer. The KEYID data slot you'd like to use for ECC calculation.

Output: Boolean

True- Both load tempKey and signTempkey commands were successful.
False- Communication failure or IC command execution failure.

.verifySignature(uint8_t *message, uint8_t *signature, uint8_t *publicKey)

Verifies an ECC digital signature against a message and an external public key.
Note, the IC actually needs you to store your message in a temporary memory location
called TempKey. This function first loads TempKey with the message, and then sends the verify command
with additional data paramaters: signature and publicKey. Then it listens for result from IC.
Input: message

Unassigned 8-bit integer pointer. (32 bytes).

Input: signature

Unassigned 8-bit integer pointer. (64 bytes)

Input: publicKey

Unassigned 8-bit integer pointer. (64 bytes)

Output: Boolean

True- Both load tempKey and verify commands were successful.
False- Verification failure, Communication failure or IC command execution failure.

Random Number Generator

.random(long max)

This function returns a positive random Long between 0 and max.
Max can be up to the larges positive value of a long: 2147483647
Input: max

Long. (1-2147483647)

Output: Long 32 bits (4 bytes)

.random(long min, long max)

This function returns a random Long between min and max.
Min and max can be positive or negative numbers.
Input: min

Long. (-2147483647 to +2147483647)

Input: max

Long. (-2147483647 to +2147483647)

Output: Long 32 bits (4 bytes)

.getRandomByte(boolean debug)

This function returns a positive random byte.
Output: Byte

.getRandomInt(boolean debug)

This function returns a positive random integer.
Output: Unassigned 16-bit integer

.getRandomLong(boolean debug)

This function returns a Long.
Output: Long 32 bits (4 bytes)r

.updateRandom32Bytes(boolean debug)

This function gets a complete random number from the IC (all 32 bytes).
It stores it in a global public variable array called .random32Bytes
If you wish to access this global variable and use as a 256 bit random number,
then you will need to access this array and combine it's elements as you wish.
Output: Boolean

True- Random command execution successful.
False- Communication failure or IC command execution failure.