From a639f284ea59e40b3d6c5551d9b19e57ef4f5fd6 Mon Sep 17 00:00:00 2001 From: myh <95896306+Anchor-x@users.noreply.github.com> Date: Wed, 5 Jun 2024 16:13:25 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=BE=E5=A4=87=E6=B3=A8=E5=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/RegisterDevice.css | 28 ++ frontend/src/components/RegisterDevice.js | 526 +++++++++++++++++++++ 2 files changed, 554 insertions(+) create mode 100644 frontend/src/components/RegisterDevice.css create mode 100644 frontend/src/components/RegisterDevice.js diff --git a/frontend/src/components/RegisterDevice.css b/frontend/src/components/RegisterDevice.css new file mode 100644 index 0000000..49a4d62 --- /dev/null +++ b/frontend/src/components/RegisterDevice.css @@ -0,0 +1,28 @@ +.steps-content { + margin-top: 16px; + border: 1px dashed #e9e9e9; + border-radius: 6px; + background-color: #fafafa; + padding: 80px 40px; +} + +.steps-action { + margin-top: 24px; +} + +.dynamic-delete-button { + cursor: pointer; + position: relative; + top: 4px; + font-size: 24px; + color: #999; + transition: all .3s; + margin-left: 8px; +} +.dynamic-delete-button:hover { + color: #777; +} +.dynamic-delete-button[disabled] { + cursor: not-allowed; + opacity: 0.5; +} \ No newline at end of file diff --git a/frontend/src/components/RegisterDevice.js b/frontend/src/components/RegisterDevice.js new file mode 100644 index 0000000..3f68b6e --- /dev/null +++ b/frontend/src/components/RegisterDevice.js @@ -0,0 +1,526 @@ +import getWeb3 from '../utils/web3'; +import DeviceManager, { getDefaultAccount } from '../DeviceManager'; + +import elliptic from 'elliptic'; +import ethWallet from 'ethereumjs-wallet'; +import { sha3, addHexPrefix, setLengthLeft } from 'ethereumjs-util'; +import { merkleRoot } from 'merkle-tree-solidity'; + +import React, { Component } from 'react'; +import './RegisterDevice.css'; + +import { Steps, Button, Input, Card, Spin, Alert, Divider, Form, Icon, Dropdown, Menu, message, notification } from 'antd'; + +const Step = Steps.Step; +const { Meta } = Card; +const EC = elliptic.ec; +const FormItem = Form.Item; + +const steps = [{ + title: 'Identifier', +}, { + title: 'Metadata', +}, { + title: 'Firmware', +}, { + title: 'Confirm', +}]; + +const openNotificationWithIcon = (type, message, description) => { + notification[type]({ + message, + description + }); +}; + +class RegisterDevice extends Component { + constructor(props) { + super(props); + this.state = this.getInitialState(); + } + + getInitialState() { + return { + loading: false, + current: 0, + identifier: '', + metadataHash: '', + firmwareHash: '', + showIdentifierInfo: false, + publicKey: '', + privateKey: '', + address: '', + metadata: [{ value: '' }], + firmware: '', + curve: '', + deviceId: '', + }; + } + + reset() { + this.setState(this.getInitialState()); + } + + async componentWillMount() { + try { + let results = await getWeb3; + + this.setState({ + web3: results.web3, + }); + } catch (error) { + console.log(error); + message.error(error.message); + } + } + + async watchForChanges(txHash) { + let instance = await DeviceManager; + let deviceCreatedEvent = instance.DeviceCreated() + + deviceCreatedEvent.watch((error, result) => { + if (!error) { + if (result.transactionHash === txHash) { + openNotificationWithIcon('success', 'Transaction mined', 'Your device has been registered.'); + this.state.deviceCreatedEvent.stopWatching(); + this.setState({ + loading: false, + deviceId: result.args.deviceId.toNumber() + }) + this.next(); + } + } else { + console.error(error); + } + }); + + this.setState({ + deviceCreatedEvent + }) + } + + next() { + const { current, identifier/*, metadataHash, firmwareHash*/ } = this.state; + + if ((current === 0) && (identifier === null || identifier === '')) { + message.error('Invalid identifier: can\'t be empty'); + //} else if ((current === 1) && (metadataHash === null || metadataHash === '')) { + // message.error('Invalid metadata hash.'); + //} else if ((current === 2) && (firmwareHash === null || firmwareHash === '')) { + // message.error('Invalid firmware hash.'); + } else { + this.setState(prevState => ({ current: prevState.current + 1 })); + } + } + prev() { + const current = this.state.current - 1; + this.setState({ current }); + } + + gotoStep(i) { + this.setState({ current: i }); + } + + handleChange(e) { + this.setState({ + [e.target.name]: e.target.value, + }); + + if (this.state.current === 0) { + this.setState({ + showIdentifierInfo: false + }); + } + + if (this.state.current === 0 && e.target.name === 'identifier') { + this.setState({ + showIdentifierInfo: false, + publicKey: '', + privateKey: '', + address: '', + curve: '' + }); + } + + if (this.state.current === 1 && e.target.name === 'metadataHash') { + this.setState({ + metadata: [{ value: '' }] + }); + } + + if (this.state.current === 2 && e.target.name === 'firmwareHash') { + this.setState({ + firmware: '' + }); + } + } + + generateEthWallet() { + console.log(`Generating new Ethereum wallet`); + const newWallet = ethWallet.generate(); + + let publicKey = newWallet.getPublicKey().toString('hex'); + let privateKey = newWallet.getPrivateKey().toString('hex'); + let address = newWallet.getAddressString(); + + console.log(`Private key: ${privateKey}`); + console.log(`Public key: ${publicKey}`); + console.log(`Address: ${address}`); + + this.setState({ + identifier: address, + showIdentifierInfo: true, + address, + publicKey, + privateKey, + curve: 'secp256k1' + }) + } + + generateEcKeyPair(curve) { + let ec = new EC(curve); + console.log(`Generating new ${curve} key pair`); + let keyPair = ec.genKeyPair(); + + let publicKey = keyPair.getPublic(true, 'hex'); + let privateKey = keyPair.getPrivate('hex'); + + console.log(`Private key: ${privateKey}`); + console.log(`Public key compressed: ${publicKey}`); + console.log(`Public key uncompressed: ${keyPair.getPublic().encode('hex')}`); + + this.setState({ + identifier: publicKey, + showIdentifierInfo: true, + address: '', + publicKey, + privateKey, + curve + }) + } + + calculateMetadataHash() { + let elements = this.state.metadata.map(el => sha3(el.value)); + console.log(`Generating Merkle root hash`); + + let metadataRootSha3 = merkleRoot(elements); + console.log(`Merkle root hash ${metadataRootSha3.toString('hex')}`); + + this.setState({ + metadataHash: metadataRootSha3.toString('hex') + }) + } + + calculateFirmwareHash() { + let firmwareHash = sha3(this.state.firmware); + + this.setState({ + firmwareHash: firmwareHash.toString('hex') + }) + } + + removeMetadataField(k) { + const { metadata } = this.state; + metadata.splice(k, 1); + this.setState({ + metadata + }) + } + + addMetadataField() { + const { metadata } = this.state; + metadata.push({ value: '' }); + this.setState({ + metadata + }); + } + + handleMetadataChange(e, index) { + const { metadata } = this.state; + metadata[index].value = e.target.value; + + this.setState({ + metadata + }); + } + + downloadConfiguration() { + const { identifier, metadataHash, firmwareHash, metadata, firmware, address, publicKey, privateKey, curve, deviceId } = this.state; + + const configuration = { + identifier, + metadataHash, + firmwareHash, + }; + + if (metadata.length > 0 && metadata[0].value !== '' && metadataHash !== '') { + configuration.metadata = metadata.map(el => el.value); + } + + if (firmware !== '' && firmwareHash !== '') { + configuration.firmware = firmware; + } + + if (address !== '') { + configuration.address = address; + } + + if (publicKey !== '') { + configuration.publicKey = publicKey; + } + + if (privateKey !== '') { + configuration.privateKey = privateKey; + } + + if (curve !== '') { + configuration.curve = curve; + } + + if (deviceId !== '') { + configuration.deviceId = deviceId; + } + + let configurationJson = JSON.stringify(configuration); + + let element = document.createElement("a"); + let file = new Blob([configurationJson], { type: 'text/json' }); + element.href = URL.createObjectURL(file); + element.download = `device_${deviceId}.json`; + element.click(); + } + + onCurveSelect({ key }) { + this.generateEcKeyPair(key); + }; + + getContentForStep(step) { + const { identifier, metadataHash, firmwareHash, metadata, firmware } = this.state; + + // Identifier + if (step === 0) { + const curves = ['p224', 'curve25519']; + const ecMenu = ( + this.onCurveSelect(e)}> + {curves.map(curve => {curve})} + + ); + return ( +
+

+ Unique device identifier is a public key or a fingerprint of RSA/ECC public key. It can also be an Ethereum address (recommended). +

+ this.handleChange(e)} + /> +

+ + + + + + + {this.state.showIdentifierInfo ? +
+
+ +
: null} +
+ ); + } + + // Metadata hash + if (step === 1) { + return ( +
+

+ Metadash hash is Merkle root hash of device information or just a hash of any data. +

+ this.handleChange(e)} + /> + +

+ If you already don't have one, you can use inputs below to generate SHA-3 (Keccak) hash. With multiple fields, Merkle tree will be used. +

+
+
+ {metadata.map((key, index) => { + return ( + + this.handleMetadataChange(e, index)} + /> + {metadata.length > 1 ? ( + this.removeMetadataField(index)} + /> + ) : null} + + ) + }) + } + + + + + + +
+
+ ); + } + + // Firmware hash + if (step === 2) { + return ( +
+

+ Firmware hash is a hash of actual firmware hash. Actual firmware hash is not supposed to be stored. +

+ this.handleChange(e)} + /> + +

+ You can use input to generate SHA-3 (Keccak) hash of any data. +

+
+ this.handleChange(e)} + /> +
+
+ +
+ ); + } + + // Overview/confirm + if (step === 3) { + return ( +
+ Identifier {identifier} this.gotoStep(0)} />
} bordered={false}> + Metadata hash {metadataHash.length > 0 ? metadataHash : 'empty'} this.gotoStep(1)} />} + description={
Firmware hash {firmwareHash.length > 0 ? firmwareHash : 'empty'} this.gotoStep(2)} />
} + /> + + + ); + } + + // Configuration + if (step === 4) { + return ( +
+ +

+

+ Click below to download device configuration. +

+
+ +
+ ); + } + } + + async createDevice() { + const { identifier, metadataHash, firmwareHash, address } = this.state; + try { + let instance = await DeviceManager; + + let identifierToSave = identifier; + if (address !== '') { + let addressToPad = address; + if (address.startsWith('0x')) { + addressToPad = addressToPad.substring(2); + } + identifierToSave = setLengthLeft(Buffer.from(addressToPad, 'hex'), 32).toString('hex'); + } + + let result = await instance.createDevice(addHexPrefix(identifierToSave), addHexPrefix(metadataHash), addHexPrefix(firmwareHash), { from: getDefaultAccount() }); + await this.watchForChanges(result.tx); + openNotificationWithIcon('info', 'Transaction sent', 'Once mined, your device will be registered.'); + this.setState({ + loading: true + }); + } catch (error) { + console.log(error); + message.error(error.message); + } + } + + render() { + const { current } = this.state; + return ( +
+ + + {steps.map(item => )} + +
{this.getContentForStep(current)}
+
+ { + current < steps.length - 1 + && + } + { + current === steps.length - 1 + && + } + { + current > 0 && current !== 4 + && ( + + ) + } + { + current === 4 + && ( + + ) + } +
+
+
+ ); + } +} + +export default RegisterDevice; \ No newline at end of file