From baff76e74d9f4071c3d7d3a75af5cc1060f4ba89 Mon Sep 17 00:00:00 2001 From: CaptainHaddock <94396037+CaptainHaddock2016@users.noreply.github.com> Date: Mon, 6 Oct 2025 18:44:45 -0700 Subject: [PATCH] Fix trailing whitespace --- adafruit_atecc/adafruit_atecc.py | 216 +++++++++++++++++++++---------- 1 file changed, 149 insertions(+), 67 deletions(-) diff --git a/adafruit_atecc/adafruit_atecc.py b/adafruit_atecc/adafruit_atecc.py index 8b0832e..8e31aba 100755 --- a/adafruit_atecc/adafruit_atecc.py +++ b/adafruit_atecc/adafruit_atecc.py @@ -36,20 +36,32 @@ * Adafruit CircuitPython firmware for the supported boards: https://github.com/adafruit/circuitpython/releases - * Adafruit Bus Device library: +* Adafruit Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice - * Adafruit binascii library: +* Adafruit binascii library: https://github.com/adafruit/Adafruit_CircuitPython_binascii """ + import time from struct import pack -from micropython import const -from adafruit_bus_device.i2c_device import I2CDevice + +# Since the board may or may not have access to the typing library we need +# to have this in a try/except to enable type hinting for the IDEs while +# not breaking the runtime on the controller. +try: + from typing import Any, Optional, Sized + + from busio import I2C +except ImportError: + pass + from adafruit_binascii import hexlify, unhexlify +from adafruit_bus_device.i2c_device import I2CDevice +from micropython import const -__version__ = "0.0.0-auto.0" +__version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ATECC.git" @@ -82,6 +94,7 @@ def _convert_i2c_addr_to_atecc_addr(i2c_addr=0x60): OP_GEN_KEY = const(0x40) OP_SIGN = const(0x41) OP_WRITE = const(0x12) +OP_ECDH = const(0x43) # Maximum execution times, in milliseconds (9-4) EXEC_TIME = { @@ -94,6 +107,7 @@ def _convert_i2c_addr_to_atecc_addr(i2c_addr=0x60): OP_GEN_KEY: const(115), OP_SIGN: const(70), OP_WRITE: const(26), + OP_ECDH: const(80), } """ @@ -112,13 +126,21 @@ def _convert_i2c_addr_to_atecc_addr(i2c_addr=0x60): I2C Config - HEX DEC BIN Description -Byte 14: C0 192 1100 0000 - ^xxx xxxx Bit 0 (MSB): 0:Single Wire, 1:I2C; Bit 1-7: Set by Microchip -Byte 16: C0 192 1100 0000 Default 7 bit I2C Address: 0xC0>>1: 0x60 ATECC608A-MAHDA -Byte 16: 6A 106 0110 1010 Default 7 bit I2C Address: 0x6A>>1: 0x35 ATECC608A-TNGTLS -Byte 16: 20 32 0010 0000 Default 7 bit I2C Address: 0x20>>1: 0x10 ATECC608A-UNKNOWN ++------+-------+---------+-------------+---------------------------------------------------------------+ +| HEX | DEC | BIN | Description | ++======+=======+=========+=============+===============================================================+ +| Byte 14: C0 | 192 | 1100 0000 | | +| | | ^xxx xxxx | Bit 0 (MSB): 0:Single Wire, 1:I2C; Bit 1-7: Set by Microchip | ++--------------+---------+-------------+---------------------------------------------------------------+ +| Byte 16: C0 | 192 | 1100 0000 | Default 7 bit I2C Address: 0xC0>>1: 0x60 ATECC608A-MAHDA | ++--------------+---------+-------------+---------------------------------------------------------------+ +| Byte 16: 6A | 106 | 0110 1010 | Default 7 bit I2C Address: 0x6A>>1: 0x35 ATECC608A-TNGTLS | ++--------------+---------+-------------+---------------------------------------------------------------+ +| Byte 16: 20 | 32 | 0010 0000 | Default 7 bit I2C Address: 0x20>>1: 0x10 ATECC608A-UNKNOWN | ++--------------+---------+-------------+---------------------------------------------------------------+ + """ + CFG_TLS = bytes( bytearray( unhexlify( @@ -154,22 +176,21 @@ class ATECC: CircuitPython interface for ATECCx08A Crypto Co-Processor Devices. """ - def __init__(self, i2c_bus, address=_REG_ATECC_DEVICE_ADDR, debug=False): - """Initializes an ATECC device. + def __init__(self, i2c_bus: I2C, address: int = _REG_ATECC_DEVICE_ADDR, debug: bool = False): + """ + Initializes an ATECC device. + :param busio i2c_bus: I2C Bus object. :param int address: Device address, defaults to _ATECC_DEVICE_ADDR. :param bool debug: Library debugging enabled - """ self._debug = debug self._i2cbuf = bytearray(12) # don't probe, the device will NACK until woken up self._wake_device = I2CDevice(i2c_bus, 0x00, probe=False) self._i2c_device = I2CDevice(i2c_bus, address, probe=False) - if (self.version() >> 8) not in (_ATECC_508_VER, _ATECC_608_VER): - raise RuntimeError( - "Failed to find 608 or 508 chip. Please check your wiring." - ) + if (self.version() >> 8) not in {_ATECC_508_VER, _ATECC_608_VER}: + raise RuntimeError("Failed to find 608 or 508 chip. Please check your wiring.") def wakeup(self): """Wakes up THE ATECC608A from sleep or idle modes.""" @@ -178,11 +199,10 @@ def wakeup(self): # clock cycles will be 80us. This signal is generated by trying to # address something at 0x00. It will fail, but the pattern should # wake up the ATECC. - # pylint: disable=bare-except try: with self._wake_device as i2c: i2c.write(bytes([0x00])) - except: + except Exception: pass time.sleep(0.001) @@ -248,7 +268,7 @@ def lock_all_zones(self): self.lock(0) self.lock(1) - def lock(self, zone): + def lock(self, zone: int): """Locks specific ATECC zones. :param int zone: ATECC zone to lock. """ @@ -260,10 +280,13 @@ def lock(self, zone): assert res[0] == 0x00, "Failed locking ATECC!" self.idle() - def info(self, mode, param=None): - """Returns device state information - :param int mode: Mode encoding, see Table 9-26. + def info(self, mode: int, param: Optional[Any] = None) -> bytearray: + """ + Returns device state information + :param int mode: Mode encoding, see Table 9-26. + :param param: Optional parameter + :return: bytearray containing the response """ self.wakeup() if not param: @@ -276,16 +299,18 @@ def info(self, mode, param=None): self.idle() return info_out - def nonce(self, data, mode=0, zero=0x0000): - """Generates a nonce by combining internally generated random number + def nonce(self, data: bytearray, mode: int = 0, zero: int = 0x0000) -> bytearray: + """ + Generates a nonce by combining internally generated random number with an input value. + :param bytearray data: Input value from system or external. :param int mode: Controls the internal RNG and seed mechanism. :param int zero: Param2, see Table 9-35. - + :return: bytearray containing the calculated nonce """ self.wakeup() - if mode in (0x00, 0x01): + if mode in {0x00, 0x01}: if zero == 0x00: assert len(data) == 20, "Data value must be 20 bytes long." self._send_command(OP_NONCE, mode, zero, data) @@ -303,19 +328,19 @@ def nonce(self, data, mode=0, zero=0x0000): self._get_response(calculated_nonce) time.sleep(1 / 1000) if mode == 0x03: - assert ( - calculated_nonce[0] == 0x00 - ), "Incorrectly calculated nonce in pass-thru mode" + assert calculated_nonce[0] == 0x00, "Incorrectly calculated nonce in pass-thru mode" self.idle() return calculated_nonce - def counter(self, counter=0, increment_counter=True): - """Reads the binary count value from one of the two monotonic + def counter(self, counter: int = 0, increment_counter: bool = True) -> bytearray: + """ + Reads the binary count value from one of the two monotonic counters located on the device within the configuration zone. The maximum value that the counter may have is 2,097,151. + :param int counter: Device's counter to increment. :param bool increment_counter: Increments the value of the counter specified. - + :return: bytearray with the count """ counter = 0x00 self.wakeup() @@ -331,18 +356,20 @@ def counter(self, counter=0, increment_counter=True): self.idle() return count - def random(self, rnd_min=0, rnd_max=0): - """Generates a random number for use by the system. + def random(self, rnd_min: int = 0, rnd_max: int = 0) -> int: + """ + Generates a random number for use by the system. + :param int rnd_min: Minimum Random value to generate. :param int rnd_max: Maximum random value to generate. - + :return: Random integer """ if rnd_max: rnd_min = 0 if rnd_min >= rnd_max: return rnd_min delta = rnd_max - rnd_min - r = bytes(16) + r = bytearray(16) r = self._random(r) data = 0 for i in enumerate(r): @@ -352,10 +379,12 @@ def random(self, rnd_min=0, rnd_max=0): data = data % delta return data + rnd_min - def _random(self, data): - """Initializes the random number generator and returns. - :param bytearray data: Response buffer. + def _random(self, data: bytearray) -> bytearray: + """ + Initializes the random number generator and returns. + :param bytearray data: Response buffer. + :return: bytearray """ self.wakeup() data_len = len(data) @@ -371,8 +400,9 @@ def _random(self, data): return data # SHA-256 Commands - def sha_start(self): - """Initializes the SHA-256 calculation engine + def sha_start(self) -> bytearray: + """ + Initializes the SHA-256 calculation engine and the SHA context in memory. This method MUST be called before sha_update or sha_digest """ @@ -385,11 +415,13 @@ def sha_start(self): self.idle() return status - def sha_update(self, message): - """Appends bytes to the message. Can be repeatedly called. + def sha_update(self, message: bytes) -> bytearray: + """ + Appends bytes to the message. Can be repeatedly called. + :param bytes message: Up to 64 bytes of data to be included into the hash operation. - + :return: bytearray containing the status """ self.wakeup() self._send_command(OP_SHA, 0x01, 64, message) @@ -400,12 +432,14 @@ def sha_update(self, message): self.idle() return status - def sha_digest(self, message=None): - """Returns the digest of the data passed to the + def sha_digest(self, message: bytearray = None) -> bytearray: + """ + Returns the digest of the data passed to the sha_update method so far. + :param bytearray message: Up to 64 bytes of data to be included into the hash operation. - + :return: bytearray containing the digest """ if not hasattr(message, "append") and message is not None: message = pack("B", message) @@ -422,11 +456,35 @@ def sha_digest(self, message=None): self.idle() return digest - def gen_key(self, key, slot_num, private_key=False): - """Generates a private or public key. + def ecdh(self, slot_num: int, public_key: bytearray, mode: int = 0x0C) -> bytearray: + """ + Performs ECDH key agreement operation. + :param int slot_num: ECC slot (0-4) containing private key. + :param bytearray public_key: 64-byte public key (X||Y). (must be curve secp256r1) + :param int mode: Mode parameter, defaults to 0x0C. + :return: bytearray containing the shared secret + """ + + assert len(public_key) == 64, "Public key must be 64 bytes (X||Y)" + + self.wakeup() + # Send ECDH command (opcode 0x43) + self._send_command(OP_ECDH, mode, slot_num, public_key) + time.sleep(EXEC_TIME[OP_ECDH] / 1000) + + response = bytearray(32) # shared secret + self._get_response(response) + self.idle() + return response + + def gen_key(self, key: bytearray, slot_num: int, private_key: bool = False) -> bytearray: + """ + Generates a private or public key. + + :param key: Buffer to put the key into :param int slot_num: ECC slot (from 0 to 4). :param bool private_key: Generates a private key if true. - + :return: The requested key """ assert 0 <= slot_num <= 4, "Provided slot must be between 0 and 4." self.wakeup() @@ -434,17 +492,20 @@ def gen_key(self, key, slot_num, private_key=False): self._send_command(OP_GEN_KEY, 0x04, slot_num) else: self._send_command(OP_GEN_KEY, 0x00, slot_num) + time.sleep(EXEC_TIME[OP_GEN_KEY] / 1000) self._get_response(key) time.sleep(0.001) self.idle() return key - def ecdsa_sign(self, slot, message): - """Generates and returns a signature using the ECDSA algorithm. + def ecdsa_sign(self, slot: int, message: bytearray) -> bytearray: + """ + Generates and returns a signature using the ECDSA algorithm. + :param int slot: Which ECC slot to use. :param bytearray message: Message to be signed. - + :return: bytearray containing the signature """ # Load the message digest into TempKey using Nonce (9.1.8) self.nonce(message, 0x03) @@ -453,9 +514,12 @@ def ecdsa_sign(self, slot, message): sig = self.sign(slot) return sig - def sign(self, slot_id): - """Performs ECDSA signature calculation with key in provided slot. + def sign(self, slot_id: int) -> bytearray: + """ + Performs ECDSA signature calculation with key in provided slot. + :param int slot_id: ECC slot containing key for use with signature. + :return: bytearray containing the signature """ self.wakeup() self._send_command(0x41, 0x80, slot_id) @@ -465,8 +529,10 @@ def sign(self, slot_id): self.idle() return signature - def write_config(self, data): - """Writes configuration data to the device's EEPROM. + def write_config(self, data: bytearray): + """ + Writes configuration data to the device's EEPROM. + :param bytearray data: Configuration data to-write """ # First 16 bytes of data are skipped, not writable @@ -476,9 +542,16 @@ def write_config(self, data): continue self._write(0, i // 4, data[i : i + 4]) - def _write(self, zone, address, buffer): + def _write(self, zone: Any, address: int, buffer: bytearray): + """ + Writes to the I2C + + :param Any zone: Zone to send to + :param int address: The address to send to + :param bytearray buffer: The buffer to send + """ self.wakeup() - if len(buffer) not in (4, 32): + if len(buffer) not in {4, 32}: raise RuntimeError("Only 4 or 32-byte writes supported.") if len(buffer) == 32: zone |= 0x80 @@ -488,9 +561,16 @@ def _write(self, zone, address, buffer): self._get_response(status) self.idle() - def _read(self, zone, address, buffer): + def _read(self, zone: int, address: int, buffer: bytearray): + """ + Reads from the I2C + + :param int zone: Zone to read from + :param int address: The address to read from + :param bytearray buffer: The buffer to read to + """ self.wakeup() - if len(buffer) not in (4, 32): + if len(buffer) not in {4, 32}: raise RuntimeError("Only 4 and 32 byte reads supported") if len(buffer) == 32: zone |= 0x80 @@ -500,8 +580,10 @@ def _read(self, zone, address, buffer): time.sleep(0.001) self.idle() - def _send_command(self, opcode, param_1, param_2=0x00, data=""): - """Sends a security command packet over i2c. + def _send_command(self, opcode: int, param_1: int, param_2: int = 0x00, data: Sized = ""): + """ + Sends a security command packet over i2c. + :param byte opcode: The command Opcode :param byte param_1: The first parameter :param byte param_2: The second parameter, can be two bytes. @@ -534,7 +616,7 @@ def _send_command(self, opcode, param_1, param_2=0x00, data=""): # small sleep time.sleep(0.001) - def _get_response(self, buf, length=None, retries=20): + def _get_response(self, buf: Sized, length: int = None, retries: int = 20) -> int: self.wakeup() if length is None: length = len(buf) @@ -559,7 +641,7 @@ def _get_response(self, buf, length=None, retries=20): return response[1] @staticmethod - def _at_crc(data, length=None): + def _at_crc(data: Sized, length: int = None) -> int: if length is None: length = len(data) if not data or not length: