IoT-device-management/contracts/DeviceManager.sol
2024-06-05 16:08:01 +08:00

320 lines
13 KiB
Solidity

pragma solidity ^0.5.0;
import "./MerkleProof.sol";
import "./ECRecovery.sol";
/**
* @title Provides base functionalities for entities.
*/
contract EntityBase {
/// @dev Entity in the device management system.
struct Entity {
// Arbitrary data in case entity wants to have some public information available.
string data;
}
/// @notice Maps owner to an Entity structure.
mapping (address => Entity) public ownerToEntity;
/// @dev Fired on entity data update.
event EntityDataUpdated(address indexed owner, string newData);
/**
* @notice Update entity data.
* @param _data Entity data.
*/
function updateEntityData(string memory _data) public {
ownerToEntity[msg.sender].data = _data;
emit EntityDataUpdated(msg.sender, _data);
}
}
/**
* @title Provides base functionalities for devices.
*/
contract DeviceBase {
/// @dev Main device structure.
struct Device {
// Ethereum address of a device owner.
address owner;
// Unique device identifier. Can hold anything that fits into 32 bytes. Different from device ID.
// Supposed to be a public key or a representation of one, such as fingerprint of RSA/ECC
// public key or simply an Ethereum address. To make identifier be an Ethereum address,
// use secp256k1 curve to generate public and private key pair, run keccak256 hash function
// on public key and take last 20 bytes of generated hash to get Ethereum address.
// If a device wants to be it's own entity, use same address as in owner property.
bytes32 identifier;
// Merkle root hash of metadata or simple hash of concatenated data.
// It is recommended to use Merkle tree to store information on device as it enables to
// prove membership of specific data by providing Merkle proof without revealing whole dataset.
bytes32 metadataHash;
// Holds a hash of actual firmware hash. Actual firmware hash is not supposed to be stored.
// Plain text or hash would expose data that is meant to be private, so "hash of hash" principle
// gives privacy and makes integrity verification possible.
bytes32 firmwareHash;
// Additional data linked to device. Can be used to store hash of encrypted firmware on IPFS.
//string offChainLink;
// Full public key (even though parties can exchange public key other ways then use
// blockchain to verify.)
//string publicKey;
}
/// @notice State variable for storing devices. Index in the array is also a device ID.
/// Array can hold a maximum of 2^256-1 entries.
Device[] public devices;
/// @notice Keeps track of total devices for each owner.
mapping (address => uint) public ownerDeviceCount;
/// @dev Fired on creation of new device.
event DeviceCreated(uint indexed deviceId, address indexed owner, bytes32 identifier, bytes32 metadataHash, bytes32 firmwareHash);
/// @dev Modifier for ensuring that the message sender is owner of provided device ID.
modifier onlyOwnerOf(uint _deviceId) {
require(devices[_deviceId].owner == msg.sender, "Only for device owner");
_;
}
/**
* @notice Creates and saves device into storage. Emits DeviceCreated.
* @param _identifier Unique device identifier, such as fingerprint of RSA/ECC public key or Ethereum address (recommended).
* @param _metadataHash Merkle root hash of metadata (recommended) or simple hash of concatenated metadata.
* @param _firmwareHash Hash of actual firmware hash.
* @return Created device ID.
*/
function createDevice(bytes32 _identifier, bytes32 _metadataHash, bytes32 _firmwareHash) public returns (uint) {
Device memory newDevice = Device(msg.sender, _identifier, _metadataHash, _firmwareHash);
uint deviceId = devices.push(newDevice) - 1;
ownerDeviceCount[msg.sender]++;
emit DeviceCreated(deviceId, msg.sender, _identifier, _metadataHash, _firmwareHash);
return deviceId;
}
}
/**
* @title Provides extra functionalities for devices.
*/
contract DeviceHelper is DeviceBase {
/**
* @notice Gets all devices owned by specified address.
* @dev Use this function instead of filtering DeviceCreated event since devices could have been transferred between owners.
* @param _owner Owner address.
* @return Array of device IDs.
*/
function getDevicesByOwner(address _owner) public view returns (uint[] memory) {
uint[] memory deviceIds = new uint[](ownerDeviceCount[_owner]);
uint counter = 0;
for (uint i = 0; i < devices.length; i++) {
if (devices[i].owner == _owner) {
deviceIds[counter] = i;
counter++;
}
}
return deviceIds;
}
/**
* @notice Checks if device is also an entity.
* @param _deviceId ID of a device.
* @return Boolean status.
*/
function isDeviceAnEntity(uint _deviceId) public view returns (bool) {
return devices[_deviceId].owner == address(uint160(uint256(devices[_deviceId].identifier)));
}
/**
* @notice Checks if provided leaf is a member of metadata contained in Merkle tree.
* Assumes that each pair of leaves and each pair of pre-images are sorted.
* @param _deviceId ID of a device containing metadata hash.
* @param _proof Merkle proof containing sibling hashes on the branch from the leaf to the root of the Merkle tree.
* @param _leaf Leaf of Merkle tree.
* @return Boolean status.
*/
function isValidMetadataMember(uint _deviceId, bytes32[] memory _proof, bytes32 _leaf) public view returns (bool) {
return MerkleProof.verifyProof(_proof, devices[_deviceId].metadataHash, _leaf);
}
/**
* @notice Checks if provided firmware hash is equal to firmware hash device property.
* @param _deviceId ID of a device containing firmware hash.
* @param _firmwareHash Firmware hash (not the actual hash).
* @return Boolean status.
*/
function isValidFirmwareHash(uint _deviceId, bytes32 _firmwareHash) public view returns (bool) {
return devices[_deviceId].firmwareHash == _firmwareHash;
}
/**
* @notice Validate authenticity of message signed by Etherium private key.
* On-chain validation is available only for Ethereum signed messages.
* @param _deviceId ID of a device that signed the message.
* @param _messageHash Hash of sent message.
* @param _signature Signature generated using web3.eth.sign().
* @return Boolean status.
*/
function isValidEthMessage(uint _deviceId, bytes32 _messageHash, bytes memory _signature) public view returns (bool) {
return ECRecovery.recover(_messageHash, _signature) == address(uint160(uint256(devices[_deviceId].identifier)));
}
}
/**
* @title Provides base functionalities for signatures.
*/
contract SignatureBase {
/// @dev Main signature structure.
struct Signature {
// Ethereum address of the signer.
address signer;
// ID of device to sign.
uint deviceId;
// Using 256 bits ensures no overflow on year 2038 (Unix seconds).
uint expiryTime;
// Updates to true once signer decides to revoke signature.
bool revoked;
}
/// @notice State variable for storing signatures. Index in the array is also a signature ID.
/// Array can hold a maximum of 2^256-1 entries.
Signature[] public signatures;
/// @notice Keeps track of total signatures for each device.
mapping (uint => uint) public deviceSignatureCount;
/// @dev Fired when an address signs a device.
event DeviceSigned(uint indexed signatureId, uint indexed deviceId, address indexed signer, uint expiryTime);
/// @dev Fired when signature is revoked.
event SignatureRevoked(uint indexed signatureId, uint indexed deviceId);
/// @dev Modifier for ensuring that the device hasn't been signed already.
modifier notSigned(uint _deviceId) {
require(deviceSignatureCount[_deviceId] == 0, "Must not be signed");
_;
}
/**
* @notice Signs a device and signature into storage. Emits DeviceSigned.
* @param _deviceId ID of to be signed device.
* @param _expiryTime Expiry time in Unix seconds.
* @return Created signature ID.
*/
function signDevice(uint _deviceId, uint _expiryTime) public returns (uint) {
Signature memory signature = Signature(msg.sender, _deviceId, _expiryTime, false);
uint signatureId = signatures.push(signature) - 1;
deviceSignatureCount[_deviceId]++;
emit DeviceSigned(signatureId, _deviceId, msg.sender, _expiryTime);
return signatureId;
}
/**
* @notice Revokes a signature. Emits SignatureRevoked.
* @param _signatureId ID of to be revoked signature.
*/
function revokeSignature(uint _signatureId) public {
require(signatures[_signatureId].signer == msg.sender, "Only for creator of the signature");
require(signatures[_signatureId].revoked == false, "Signature mustn't be revoked already");
Signature storage signature = signatures[_signatureId];
signature.revoked = true;
deviceSignatureCount[signature.deviceId]--;
emit SignatureRevoked(_signatureId, signature.deviceId);
}
}
/**
* @title Provides extra functionalities for signatures.
*/
contract SignatureHelper is SignatureBase {
/**
* @notice Gets all signatures for specific device.
* @dev Use this function instead of filtering DeviceSigned event since signatures could have been revoked.
* @param _deviceId ID of a device.
* @return Array of signature IDs.
*/
function getActiveSignaturesForDevice(uint _deviceId) public view returns (uint[] memory) {
uint[] memory signatureIds = new uint[](deviceSignatureCount[_deviceId]);
uint counter = 0;
for (uint i = 0; i < signatures.length; i++) {
if (signatures[i].deviceId == _deviceId && signatures[i].revoked == false) {
signatureIds[counter] = i;
counter++;
}
}
return signatureIds;
}
}
/**
* @title Enriches devices giving them option to be updated only if not signed already.
*/
contract DeviceUpdatable is DeviceHelper, SignatureHelper {
/// @dev Fired on device ownership transfer, keeps track of historical device owners.
event DeviceTransfered(uint indexed deviceId, address oldOwner, address newOwner);
/// @dev Fired on device property update, keeps track of historical property values.
event DevicePropertyUpdated(uint indexed deviceId, bytes32 indexed property, bytes32 newValue);
/**
* @notice Transfer device ownership from one external account to another. Emits DeviceTransfered.
* @param _deviceId ID of to be transferred device.
* @param _to Address of new owner.
*/
function transferDevice(uint _deviceId, address _to) public onlyOwnerOf(_deviceId) notSigned(_deviceId) {
address currentOwner = devices[_deviceId].owner;
devices[_deviceId].owner = _to;
ownerDeviceCount[msg.sender]--;
ownerDeviceCount[_to]++;
emit DeviceTransfered(_deviceId, currentOwner, _to);
}
/**
* @notice Update device with new identifier. Emits DevicePropertyUpdated.
* @param _deviceId ID of a device.
* @param _newIdentifier New identifier.
*/
function updateIdentifier(uint _deviceId, bytes32 _newIdentifier) public onlyOwnerOf(_deviceId) notSigned(_deviceId) {
devices[_deviceId].identifier = _newIdentifier;
emit DevicePropertyUpdated(_deviceId, "identifier", _newIdentifier);
}
/**
* @notice Update device with new metadata hash. Emits DevicePropertyUpdated.
* @param _deviceId ID of a device.
* @param _newMetadataHash New metadata hash.
*/
function updateMetadataHash(uint _deviceId, bytes32 _newMetadataHash) public onlyOwnerOf(_deviceId) notSigned(_deviceId) {
devices[_deviceId].metadataHash = _newMetadataHash;
emit DevicePropertyUpdated(_deviceId, "metadata", _newMetadataHash);
}
/**
* @notice Update device with new firmware hash. Emits DevicePropertyUpdated.
* @param _deviceId ID of a device.
* @param _newFirmwareHash New firmware hash.
*/
function updateFirmwareHash(uint _deviceId, bytes32 _newFirmwareHash) public onlyOwnerOf(_deviceId) notSigned(_deviceId) {
devices[_deviceId].firmwareHash = _newFirmwareHash;
emit DevicePropertyUpdated(_deviceId, "firmware", _newFirmwareHash);
}
}
/// @title Device manager core contract.
contract DeviceManager is EntityBase, DeviceUpdatable {
/// @dev Merges contracts.
}