Oracle-mediated validation and blockchain anchoring for a simulated Digital Twin
Final Year Project — BSc (Hons) Computer Science, Staffordshire University Author: Chidube Steve Anike
TrustFlow-DT is a proof-of-concept system that validates signed IoT telemetry before it updates a Digital Twin or is committed to a blockchain. It combines a simulated temperature sensor, MQTT messaging, a fail-closed local oracle, Digital Twin command logic, deterministic batching, IPFS storage, Chainlink Runtime Environment (CRE), and Solidity contracts deployed on Polygon Amoy.
The central design principle is that blockchain immutability protects data after commitment; it does not establish whether the original sensor reading was authentic, current, unique, or physically plausible. TrustFlow-DT therefore places an explicit validation boundary before both Digital Twin consumption and blockchain anchoring.
This repository is an academic proof of concept. It is not production-ready infrastructure and must not be used to control real equipment.
| Layer | Implemented component | Main location |
|---|---|---|
| 1. Physical simulation | Temperature generator, ECDSA secp256k1 signing, and controlled anomaly injection |
src/simulation/, src/mqtt/publisher.ts |
| 2. Messaging | MQTT raw, validated, anomaly, and command streams | src/config/mqttClient.ts, src/mqtt/ |
| 3. Local validation | Registry status, signature, timestamp, nonce, threshold, and rate-of-change checks | src/validation/ |
| 4. Relay and CRE | Per-device batching, deterministic hashing, IPFS pinning, HTTP relay, and CRE field consensus | src/relay/, cre/trustflow-anchor-workflow/ |
| 5. Blockchain | Device lifecycle, role-based anchoring, CRE receiver, events, and public hash verification | src/blockchain/ |
| 6. Digital Twin | Live accepted-state processing, anomaly responses, and QoS 2 command publication | src/scripts/runDTCore.ts |
The latency-sensitive Digital Twin path is independent of blockchain confirmation. Validated telemetry can update the twin immediately, while the same validated stream is batched and anchored asynchronously.
| Topic | QoS | Purpose |
|---|---|---|
trustflow/raw/{deviceId} |
1 | Signed telemetry from the simulated device |
trustflow/validated/{deviceId} |
1 | Readings accepted by the oracle |
trustflow/anomaly/{deviceId} |
1 | Rejected readings and reason codes |
trustflow/commands/{deviceId} |
2 | Commands issued by the Digital Twin Core |
- Node.js and TypeScript
- Mosquitto and MQTT.js
- Node.js
cryptowith ECDSA onsecp256k1 - Express
- Pinata and IPFS
- Chainlink Runtime Environment
- Solidity 0.8.28, Hardhat 3, Ethers.js, Mocha, and Chai
- Polygon Amoy testnet
For the local telemetry and evaluation path:
- Node.js 20 or later
- npm
- Mosquitto broker and command-line clients
For the complete anchoring path:
- a Pinata account and JWT
- a Polygon Amoy RPC endpoint
- funded Polygon Amoy accounts
- the Chainlink CRE CLI
- Bun for the CRE workflow dependencies
- a public HTTPS tunnel, such as ngrok, when CRE must reach the local relay
git clone https://github.com/chidubesteve/FYP.git
cd FYP
npm installmacOS or Linux:
cp .env.example .envWindows PowerShell:
Copy-Item .env.example .envFor the local path, confirm these values in .env:
TRUSTFLOW_BROKER_URL=mqtt://localhost:1883
TRUSTFLOW_ANCHOR_DEVICE_ID=device-001
TRUSTFLOW_KEY_PATH=keys/device-001.json
TRUSTFLOW_POLICY=normal
TRUSTFLOW_REGISTRY_PATH=config/devices.json
TRUSTFLOW_INTERVAL_MS=1000
TRUSTFLOW_DURATION_MS=0
TRUSTFLOW_ENABLE_REGISTRY_SYNC=falseDo not commit .env, wallet private keys, Pinata credentials, or files in
keys/.
mosquittoIn a second terminal:
npx tsx src/mqtt/publisher.tsOn its first run, the publisher creates
keys/device-001.json. Stop it with Ctrl+C.
The oracle verifies the publisher against config/devices.json. If a new key
was generated, copy the generated public key only into the publicKey
field for device-001 in that registry. Never copy the private key into the
registry.
npx tsx src/scripts/runOracle.tsThe oracle subscribes to trustflow/raw/#. A valid reading is republished to
trustflow/validated/{deviceId}; a rejected reading is published to
trustflow/anomaly/{deviceId} with its rejection reason.
In another terminal:
npx tsx src/scripts/runDTCore.tsThe Digital Twin Core listens to the validated and anomaly streams, maintains
the latest accepted state in memory, and publishes operational or
trust-response commands to trustflow/commands/{deviceId} at QoS 2.
To inspect those commands:
mosquitto_sub -h localhost -p 1883 -t "trustflow/commands/#" -vnpx tsx src/mqtt/publisher.tsStop each long-running service with Ctrl+C.
Keep Mosquitto and the oracle running. Do not start a separate publisher or
attacker: runEvaluation.ts starts and stops the required traffic generators.
Running another publisher at the same time contaminates the experiment counts.
macOS or Linux:
TRUSTFLOW_EXPERIMENT=mixed-100 npx tsx src/scripts/runEvaluation.tsWindows PowerShell:
$env:TRUSTFLOW_EXPERIMENT="mixed-100"
npx tsx src/scripts/runEvaluation.tsAvailable experiments are:
| Experiment | Purpose |
|---|---|
baseline |
Normal signed telemetry with no deliberate anomaly |
sensor-spike |
Out-of-range sensor spikes |
sensor-drift |
Gradually drifting sensor readings |
sensor-frozen |
Repeated constant values; a documented negative case |
sensor-rate-spike |
Excessive rate-of-change events |
attack-replay |
Reuse of a previously accepted signed reading |
attack-forged-sig |
Telemetry carrying an invalid signature |
attack-unknown-device |
Telemetry from an unregistered device |
attack-stale |
Correctly formed but stale telemetry |
mixed-100 |
Combined sensor anomalies and attacker actions |
The evaluator correlates injected readings and oracle decisions by nonce and reports true positives, false negatives, false positives, wrong-reason classifications, precision, recall, F1 score, and publish-to-validation latency.
Current evaluation-harness caveat:
runEvaluation.tsdoes not yet pass its generatedTRUSTFLOW_RUN_IDtorunAttacker.ts, and the sensor-spike injector recordsspikewhile the oracle emitsout_of_range. Until those two mappings are aligned, attacker rejections can appear as false positives and detected spikes can appear under “wrong reason�. The oracle decisions remain visible, but those affected aggregate metrics should not be treated as final results.
Injection logs are written under keys/ and are excluded from version control.
With Mosquitto and the oracle running:
npx tsx src/scripts/runFuzz.tsOverride the default number of cases per category if required:
TRUSTFLOW_FUZZ_ITERATIONS=100 npx tsx src/scripts/runFuzz.tsPowerShell:
$env:TRUSTFLOW_FUZZ_ITERATIONS="100"
npx tsx src/scripts/runFuzz.tsAdd the following to .env:
PINATA_JWT=your_pinata_jwt
TRUSTFLOW_RELAY_PORT=4000Start Mosquitto and the oracle, then start the relay:
npx tsx src/relay/relay.tsThe relay subscribes only to trustflow/validated/# and maintains a separate
pending batch for each device. After validated readings have accumulated,
finalise and pin the current batch:
curl -X POST http://localhost:4000/batch/device-001/finaliseUseful endpoints:
| Method and path | Purpose |
|---|---|
GET /health |
Relay status and batch counts |
POST /batch/:deviceId/finalise |
Freeze, hash, and pin the pending batch |
GET /batch/:deviceId/latest |
Latest finalised commitment for a device |
GET /batch/id/:batchId |
Retrieve a finalised batch by ID |
GET /batch/hash/:hash |
Retrieve a finalised batch by composite hash |
Batch finalisation is currently triggered manually. Automatic size-based or time-based finalisation is a future extension.
npm run typecheck
npm run typecheck:hardhat
npm run compile:contracts
npm run test:contractsThe contract suite covers access control, device registration and lifecycle changes, anchoring validation, events, and public batch-hash verification.
Hardhat secrets should be stored in its encrypted keystore rather than committed
to .env:
npx hardhat keystore set ALCHEMY_POLYGON_AMOY_RPC_URL
npx hardhat keystore set POLYGON_AMOY_PRIVATE_KEY
npx hardhat keystore set ORACLE_PRIVATE_KEY
npx hardhat keystore set ETHERSCAN_API_KEYDeploy the main contract:
npx hardhat run src/blockchain/scripts/deploy.ts --network polygonAmoyThe script writes deployment metadata to deployments/.
Deploy the CRE receiver after setting the forwarder address:
macOS or Linux:
TRUSTFLOW_CRE_FORWARDER_ADDRESS=0x... \
npx hardhat run src/blockchain/scripts/deployCREReceiver.ts --network polygonAmoyWindows PowerShell:
$env:TRUSTFLOW_CRE_FORWARDER_ADDRESS="0x..."
npx hardhat run src/blockchain/scripts/deployCREReceiver.ts --network polygonAmoyGrant the deployed receiver the main contract's oracle role:
npx hardhat run src/blockchain/scripts/assignOracleRoleToCREReceiver.ts --network polygonAmoyThe repository also contains scripts to update device status, repair the
configured forwarder address, generate an oracle wallet, and exercise the
earlier direct anchoring route. See src/blockchain/scripts/.
To reconcile the local oracle registry with the current deployed contract at startup and listen for later lifecycle events:
TRUSTFLOW_ENABLE_REGISTRY_SYNC=true
ALCHEMY_POLYGON_AMOY_RPC_URL=https://polygon-amoy.g.alchemy.com/v2/...Then start the oracle normally:
npx tsx src/scripts/runOracle.tsThe synchroniser resolves on-chain device hashes only for devices already known
in config/devices.json.
The original proposal used Chainlink Functions. The implemented version uses the Chainlink Runtime Environment.
-
Start the relay and collect validated readings.
-
Finalise the device batch.
-
Expose the relay through a public HTTPS URL.
-
Update
relayUrl,receiverAddress, and other settings incre/trustflow-anchor-workflow/config.staging.json. -
Install the workflow dependencies:
cd cre/trustflow-anchor-workflow bun install cd ../..
-
From the repository root, simulate the workflow:
cre workflow simulate ./cre/trustflow-anchor-workflow --target=staging-settings
For a broadcast simulation, provide a funded CRE_ETH_PRIVATE_KEY through the
CRE-supported secret or environment configuration and enable broadcasting when
prompted by the CLI.
The workflow fetches the latest finalised commitment, applies identical
field-level consensus to the device ID, batch hash, reading count, and IPFS CID,
creates a signed report, and submits it through TrustFlowCREReceiver.
The checked-in CRE configuration contains demonstration endpoints and addresses. Verify them before each run; public tunnel URLs and deployments may change.
.
├── config/
│ └── devices.json Local device registry
├── cre/
│ └── trustflow-anchor-workflow/ CRE workflow and target configuration
├── deployments/ Polygon Amoy deployment metadata
├── images/ Implementation and evaluation evidence
├── src/
│ ├── blockchain/
│ │ ├── contracts/ TrustFlowDT and CRE receiver contracts
│ │ ├── scripts/ Deployment and administration scripts
│ │ └── tests/ Hardhat contract tests
│ ├── config/ MQTT and oracle policies
│ ├── mqtt/ Publisher and subscriber infrastructure
│ ├── relay/ Batch relay and Pinata integration
│ ├── scripts/ Oracle, DT, attacker, fuzz, and evaluator
│ ├── simulation/ Generator, experiments, and injectors
│ └── validation/ Oracle and registry synchronisation
├── .env.example
├── hardhat.config.ts
├── logbook.md
├── package.json
└── tsconfig.json
The oracle rejects a reading using one of these structured reasons:
unknown_devicedevice_revokeddevice_compromisedinvalid_signaturestale_timestampfuture_timestampreplay_detectedout_of_rangerate_anomalyunexpected_error
Validation is fail-closed: a reading reaches the validated MQTT namespace only after all applicable checks pass.
- Device identities and private keys are software-generated for simulation; production use would require hardware-backed key storage and attestation.
- Relay batches are stored in memory and are lost when the relay restarts.
- Batch finalisation is manual.
- The frozen-sensor case is not detected by pointwise threshold and rate-of-change rules; window-based stagnation detection is future work.
- The CRE path has been demonstrated through workflow simulation with broadcasting. It is not presented as a production DON deployment.
- Polygon Amoy is a public test network. Testnet confirmation time is separate from the real-time MQTT validation-latency measurement.
- The Digital Twin state model is in memory and command execution by a physical actuator is simulated rather than verified.
Academic project — all rights reserved. Not licensed for commercial use.
✕Merlin
