Revamping IoT con Blockchain: un dimostratore Smart Contract per anti-tampering
Quando un'attività viene periodicamente eseguita e registrata online, cosa potrebbe fare il gestore del servizio per garantire che non modificherà i dati storici?
![Revamping IoT con Blockchain: un dimostratore Smart Contract per anti-tampering](https://digitalpress.fra1.cdn.digitaloceanspaces.com/b1c48dx/2022/02/shubham-dhage-geJHvrH-CgA-unsplash.jpeg)
Descrizione del problema
Un'attività viene periodicamente eseguita e registrata: per esempio, un insieme di sensori raccoglie dei dati fisici e li memorizza su di un server, esponendo pubblicamente i dati raccolti tramite una pagina web e un webservice JSON
.
![](https://digitalpress.fra1.cdn.digitaloceanspaces.com/b1c48dx/2022/02/sm_01.jpg)
Questo sistema ha un punto centralizzato per salvare e mostrare i dati. Cosa potrebbe modificare il gestore del servizio per garantire che non altererà i dati storici?
Soluzioni scartate
- Una prima idea è basata su file: firmando periodicamente, con cifratura asimmetrica, dei blocchi di dati raccolti, per poi mandarli a un servizio di marcatura temporale. Potrebbe funzionare ma è scomoda la verifica che non ci sia stato tampering rispetto al webservice;
- una soluzione alternativa è di utilizzare IPFS costruendo una catena di file relativi ai vari blocchi di dati: sarebbero immutabili ma senza date;
- si potrebbero usare soluzioni pronte basate su blockchain: ci sono OpenTimestamps e originstamp; la prima è gratuita ma molto laboriosa, mentre la seconda è semplice ma troppo cara per piccole realtà.
La soluzione proposta
Il Proof Of Concept qui descritto potrebbe essere una soluzione semplice e a portata di piccoli progetti.
![](https://digitalpress.fra1.cdn.digitaloceanspaces.com/b1c48dx/2022/02/sm_02.jpg)
Descriviamo brevemente i passi del processo nell'architettura del diagramma precedente.
- I dati raccolti dai sensori vengono memorizzati in una tabella di un DB e resi disponibili in lettura tramite sia un webservice che una pagina web;
- periodicamente, blocchi di questi dati vengono serializzati (per esempio in un
JSON
) e di questa stringa viene calcolato un hash; - tramite un client web3, l'hash viene inviato in scrittura a uno smart contract;
- lo smart contract memorizza nel proprio stato un hash calcolato sulla concatenazione di due stringhe: l'hash di stato e l'hash in ingresso (si veda il diagramma seguente);
- l'hash risultante viene memorizzato come nuovo hash di stato (che, alla creazione dello smart contract, sarà inizializzato da una stringa nota, per esempio nulla).
![](https://digitalpress.fra1.cdn.digitaloceanspaces.com/b1c48dx/2022/02/sm_03.jpg)
Ogni transazione sulla blockchain ha un timestamp non modificabile da chi raccoglie i dati e, se questi ultimi venissero alterati, l'hash dell'algoritmo descritto, partendo dai dati del webservice centralizzato, sarebbe diverso da quello memorizzato nello smart contract.
È di fatto una applicazione di un caso particolare di Merkle tree sbilanciato ed è anche robusto alle variazioni del JSON
(si decidesse per esempio di aumentare il numero di campi sul DB) perché quel che viene gestito è un hash, che è indifferente alla semantica della stringa da lui elaborata.
Codice
Il codice è disponibile su GitHub come atpoc (anti tampering (smart contract) proof of concept) e a quel repository si deve fare riferimento: sono qui riportate solo le parti essenziali per l'articolo stesso, mentre su GitHub si trovano anche altri metodi (per le transazioni, per esempio).
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
import "../node_modules/@openzeppelin/contracts/utils/Strings.sol";
contract Mlist {
address owner;
uint hashInternal;
constructor(string memory _hashString) {
owner = msg.sender;
hashInternal = uint(keccak256(abi.encodePacked(_hashString)));
}
function concatenate(string memory a, string memory b) internal pure returns (string memory) {
return string(abi.encodePacked(a, b));
}
function gethashInternalAsString() public view returns(string memory) {
return Strings.toHexString(hashInternal);
}
function sethashInternal(string memory _hashString) public {
require(msg.sender == owner, "Not the owner");
string memory hashInternalString = Strings.toHexString(hashInternal);
string memory stringNew = concatenate(hashInternalString, _hashString);
hashInternal = uint(keccak256(abi.encodePacked(stringNew)));
}
}
Il semplice smart contract in Solidity realizza quanto descritto, consentendo le operazioni in scrittura sull'hash interno solo da parte di chi ha pubblicato il contratto.
let Mlist = artifacts.require("Mlist")
module.exports = (deployer) => {
deployer.deploy(Mlist, "")
}
Il codice di inizializzazione imposta l'hash interno al valore equivalente a quello dato da una stringa vuota.
const truffleAssert = require('truffle-assertions')
const Mlist = artifacts.require("Mlist")
contract('Mlist', (accounts) => {
const aString = '0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb' // 'a'
let mList
before(async () => {
mList = await Mlist.deployed()
})
it('It should return 0xc5d...470', async () => {
const hashInternalAsString = await mList.gethashInternalAsString()
const emptyStringHash = '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'
assert.equal(hashInternalAsString, emptyStringHash)
})
it('It should set hashInternal to 0xbea...3ae', async() => {
const combinedString = '0xbea7774072254bb0d224ea4ea5daeabc6b2520620334696fde6408429355e3ae' // void + 'a'
await mList.sethashInternal(aString, {from: accounts[0]})
const hashInternalAsString = await mList.gethashInternalAsString()
assert.equal(hashInternalAsString, combinedString)
})
it("It should abort with an error", async () => {
await truffleAssert.reverts(mList.sethashInternal(aString, {from: accounts[1]}), "Not the owner")
})
})
I test unitari verificano le proprietà di base, compreso il fallimento per il tentativo di scrittura da parte di indirizzi diversi (l'array di account a cui si riferiscono i test è quello del classico ambiente di sviluppo locale basato su Truffle e Ganache, con l'aggiunta di comode librerie come OpenZeppelin Contracts e truffle-assertions).
Conclusioni e note
Come scritto, può essere una soluzione Proof of Concept per piccoli archivi. I principali temi che andrebbero ancora sviluppati o i problemi che si presentano, sono questi:
- non è una soluzione completamente decentralizzata, perché il gestore del server può per esempio creare e usare un nuovo smart contract contenente l'hash di dati “truccati”, indicandolo sul sito in sostituzione del precedente: in questo caso, uno smart contract oracle indipendente potrebbe vigilare (tema che meriterebbe una trattazione separata);
- non è stato descritto il client web3 necessario per la comunicazione tra il sistema legacy e lo smart contract;
- lo smart contract, per brevità didattica, non espone un metodo per il trasferimento degli eventuali ETH a lui inviati per sbaglio e, in questo caso, sarebbero resi indefinitamente indisponibili;
- si sarebbe potuto scrivere il contratto in Vyper e non in Solidity.
--
Photo by Shubham Dhage on Unsplash