From a6aa09e555e3c93870273e5670871b9aea8247df Mon Sep 17 00:00:00 2001 From: myh <95896306+Anchor-x@users.noreply.github.com> Date: Wed, 5 Jun 2024 16:08:01 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=BE=E5=A4=87=E7=AE=A1=E7=90=86Solidity?= =?UTF-8?q?=E5=90=88=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/DeviceManager.sol | 319 ++++++++++++++++++++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 contracts/DeviceManager.sol diff --git a/contracts/DeviceManager.sol b/contracts/DeviceManager.sol new file mode 100644 index 0000000..5b0c3dc --- /dev/null +++ b/contracts/DeviceManager.sol @@ -0,0 +1,319 @@ +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. +}