# MEP-02: Provisioning Contract
**Created**: 2023-04-19
## Simple Summary
Specification allows user to create and manage the PID NFTs (Provisioning ID) which are the digital representation of the sensor devices.
## Abstract
Each sensor device is provisioned on the ChirpVM with a help of a NFT. The NFT is a digital representation of the device and contains all the information needed by the ChirpVM to successufly establish a LPWAN communication channel. The ownership of the device is claimed by the ownership of the NFT.
In regards to the above, the MEP-02 proposal will extend the ERC721 interface.
## Motivation
The goal is to allow user to create, add and manange sensor devices as NFTs.
## Terminology
- PID - Provision ID
- DevEUI - Device Extended Unique Identifier
- BO - Business Owner; the entity that created the application
- Sensor Owner - entity owning the physical sensor device
## Specification
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
Every MEP-02 compliant contract must implement the ERC721 interface:
```solidity=
pragma solidity ^0.4.20;
/// @title MEP-02 Provisioning Contract
interface IMEP02 /* is IERC721 */ {
/// @dev This event gets emitted when the BO provisiones devices.
/// The parameters are the NFT id and device EUI of the sensor.
event ProvisionDevice(uint256 indexed _tokenId, string _devEUI);
/// @dev This event gets emitted when a sensor owner claims a device.
/// The parameters are the NFT id and PID of the device.
event ClaimDevice(uint256 indexed _tokenId, bytes _pid);
/// @dev This event gets emitted when a sensor owner claims a device.
/// The parameters are the NFT id and PID of the device.
event RenewDevice(uint256 indexed _tokenId, uint256 _expirationBlock);
/// @notice Returns the sensor's address
///
/// @param _tokenId The identifier of the NFT
/// @return Public address of the sensor
function addressOf(uint256 _tokenId) external view returns (address);
/// @notice Checks whether the sensor validity period expired
///
/// @param _tokenId The identifier of the NFT
/// @return True if the expiration period is greater than
/// the current height
function isValid(uint256 _tokenId) external view returns (bool);
/// @notice Provisions a new device with the given DevEUI
/// @dev This method is minting the sensor NFT and storing the
/// hash of the PID for later activation.
///
/// @param _tokenId The identifier of the NFT
/// @param _profileId The id of the device profile on MEP03
/// @param _devEUI Sensor DevEUI
/// @param _pIDHash Hash of the PID: keccak256(PID)
function provisionDevice(uint256 _tokenId, uint256 _profileId, string _devEUI, bytes _pIDHash) external payable;
/// @notice Renews the validity of the device
/// @dev This method extends the validity period of the sensor.
/// This method gets called by the sensor owner.
/// For more info check the rationale.
///
/// @param _tokenId The identifier of the NFT
/// @param _expirationBlock The block number where the
/// sensor isn't valid until renewed
function renewDevice(uint256 _tokenId, uint256 _expirationBlock) external payable;
/// @notice Claims an NFT by transferring it to the caller
/// @dev This method calculates the hash of the _pid
/// and checks if it's the same on the NFT.
/// If correct, the NFT gets transferred to the caller.
/// This method starts the validity period of the sensor.
///
/// @param _tokenId The identifier of the NFT
/// @param _expirationBlock The block number where the
/// sensor isn't valid until renewed again
/// @param _pid The PID matching the hash on the NFT
function claimDevice(uint256 _tokenId, uint256 _expirationBlock, bytes _pid) external payable;
}
interface IERC721 {
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
function balanceOf(address _owner) external view returns (uint256);
function ownerOf(uint256 _tokenId) external view returns (address);
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
function approve(address _approved, uint256 _tokenId) external payable;
function setApprovalForAll(address _operator, bool _approved) external;
function getApproved(uint256 _tokenId) external view returns (address);
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}
```
## Rationale
### Sensor validity
The sensor NFT validity acts as a way of constraining outdated (or not active) sensors. Each sensor has to have a expiration time (calculated in block height). When the current block height reaches the expiration block, the sensor is considered to be inactive. The end-user can renew the sensor by paying a fee proportional to the specified `expirationBlock` parameter.
### Front-running attack
When the device owner gets the sensor physically, he needs to claim the NFT by calling the `claim` method with the PID from the device's QR code.
A malicious observer looking at the pending TX pool can spot the claim transaction with the correct PID and replay the transaction with a higher gas price, effectively stealing the NFT from the original owner.
One potential way of preventing this is to implement a proper pre-commit scheme.
An implementer can introduce a `commitment` method which maps the `msg.sender` to a precommit hash:
`mapping(address => bytes) commitments;`
The precommit hash could be `keccak256(abi.encodePacked(PID, msg.sender))`.
Afterwards, when `claim` gets called, the method checks if there exists a matching hash for the given msg.sender.
```solidity
function claimDevice(uint256 _tokenId, uint256 _expirationBlock, bytes _pid) {
bytes commitment = commitments[msg.sender];
bytes hash = keccak256(abi.encodePacked(_pid, msg.sender));
require(commitment == hash);
...
}
```