diff --git a/contract/registrar.sol b/contract/registrar.sol index ed9a25f49fa6df1b427aa47098348bb1d9167c71..2ca7910a0c33a002c37fd9428784829dd7dc6835 100644 --- a/contract/registrar.sol +++ b/contract/registrar.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.0; +pragma solidity ^0.5.2; /* * Copyright (c) 2014 Gav Wood <g@ethdev.com> * Copyright (c) 2016 Savoir-faire Linux Inc. @@ -26,24 +26,26 @@ pragma solidity ^0.4.0; */ contract NameRegister { - function addr(bytes32 _name) constant returns (address o_owner) {} - function name(address _owner) constant returns (bytes32 o_name) {} + function addr(bytes32 _name) public view returns (address o_owner) {} + function name(address _owner) public view returns (bytes32 o_name) {} } contract Registrar is NameRegister { event Changed(bytes32 indexed name); event PrimaryChanged(bytes32 indexed name, address indexed addr, address owner); - function owner(bytes32 _name) constant returns (address o_owner) {} - function addr(bytes32 _name) constant returns (address o_address) {} - function subRegistrar(bytes32 _name) constant returns (address o_subRegistrar) {} - function content(bytes32 _name) constant returns (bytes32 o_content) {} + function owner(bytes32 _name) public view returns (address o_owner) {} + function addr(bytes32 _name) public view returns (address o_address) {} + function subRegistrar(bytes32 _name) public view returns (address o_subRegistrar) {} + function content(bytes32 _name) public view returns (bytes32 o_content) {} - function name(address _owner) constant returns (bytes32 o_name) {} + function name(address _owner) public view returns (bytes32 o_name) {} } contract GlobalRegistrar is Registrar { struct Record { + string publicKey; + string signedName; address owner; address primary; address subRegistrar; @@ -52,68 +54,70 @@ contract GlobalRegistrar is Registrar { uint renewalDate; } - function Registrar() { - } - - function reserve(bytes32 _name, address _a) { - if (m_toRecord[_name].owner == 0 && m_toName[_a] == 0) { + function reserve(bytes32 _name, address _a) public { + if (m_toRecord[_name].owner == address(0) && m_toName[_a] == 0) { m_toRecord[_name].owner = msg.sender; m_toRecord[_name].primary = _a; m_toName[_a] = _name; - Changed(_name); - PrimaryChanged(_name, _a, msg.sender); + emit Changed(_name); + emit PrimaryChanged(_name, _a, msg.sender); } } - function reserveFor(bytes32 _name, address _owner, address _a) { - if (m_toRecord[_name].owner == 0 && m_toName[_a] == 0) { + function reserveFor(bytes32 _name, address _owner, address _a, string memory _publickey, string memory _signedname) public { + if (m_toRecord[_name].owner == address(0) && m_toName[_a] == 0) { m_toRecord[_name].owner = _owner; m_toRecord[_name].primary = _a; + m_toRecord[_name].publicKey = _publickey; + m_toRecord[_name].signedName = _signedname; + m_toName[_a] = _name; - Changed(_name); - PrimaryChanged(_name, _a, _owner); + emit Changed(_name); + emit PrimaryChanged(_name, _a, _owner); } } modifier onlyrecordowner(bytes32 _name) { if (m_toRecord[_name].owner == msg.sender) _; } - function transfer(bytes32 _name, address _newOwner) onlyrecordowner(_name) { + function transfer(bytes32 _name, address _newOwner) public onlyrecordowner(_name) { m_toRecord[_name].owner = _newOwner; - Changed(_name); + emit Changed(_name); } - function disown(bytes32 _name) onlyrecordowner(_name) { + function disown(bytes32 _name) public onlyrecordowner(_name) { if (m_toName[m_toRecord[_name].primary] == _name) { - PrimaryChanged(_name, m_toRecord[_name].primary, m_toRecord[_name].owner); + emit PrimaryChanged(_name, m_toRecord[_name].primary, m_toRecord[_name].owner); m_toName[m_toRecord[_name].primary] = ""; } delete m_toRecord[_name]; - Changed(_name); + emit Changed(_name); } - function setAddress(bytes32 _name, address _a, bool _primary) onlyrecordowner(_name) { + function setAddress(bytes32 _name, address _a, bool _primary) public onlyrecordowner(_name) { m_toRecord[_name].primary = _a; if (_primary) { - PrimaryChanged(_name, _a, m_toRecord[_name].owner); + emit PrimaryChanged(_name, _a, m_toRecord[_name].owner); m_toName[_a] = _name; } - Changed(_name); + emit Changed(_name); } - function setSubRegistrar(bytes32 _name, address _registrar) onlyrecordowner(_name) { + function setSubRegistrar(bytes32 _name, address _registrar) public onlyrecordowner(_name) { m_toRecord[_name].subRegistrar = _registrar; - Changed(_name); + emit Changed(_name); } - function setContent(bytes32 _name, bytes32 _content) onlyrecordowner(_name) { + function setContent(bytes32 _name, bytes32 _content) public onlyrecordowner(_name) { m_toRecord[_name].content = _content; - Changed(_name); + emit Changed(_name); } - function owner(bytes32 _name) constant returns (address) { return m_toRecord[_name].owner; } - function addr(bytes32 _name) constant returns (address) { return m_toRecord[_name].primary; } - function register(bytes32 _name) constant returns (address) { return m_toRecord[_name].subRegistrar; } - function content(bytes32 _name) constant returns (bytes32) { return m_toRecord[_name].content; } - function name(address _a) constant returns (bytes32 o_name) { return m_toName[_a]; } + function owner(bytes32 _name) public view returns (address) { return m_toRecord[_name].owner; } + function addr(bytes32 _name) public view returns (address) { return m_toRecord[_name].primary; } + function register(bytes32 _name) public view returns (address) { return m_toRecord[_name].subRegistrar; } + function content(bytes32 _name) public view returns (bytes32) { return m_toRecord[_name].content; } + function name(address _a) public view returns (bytes32 o_name) { return m_toName[_a]; } + function publickey(bytes32 _name) public view returns (string memory) { return m_toRecord[_name].publicKey; } + function signature(bytes32 _name) public view returns (string memory) { return m_toRecord[_name].signedName; } mapping (address => bytes32) m_toName; mapping (bytes32 => Record) m_toRecord; diff --git a/index.js b/index.js index 541c429d1f84768e1d8e35f83b039d75db01479e..caf17177e4128d584c7bc2ebf5e34a8473e59c4c 100755 --- a/index.js +++ b/index.js @@ -18,15 +18,38 @@ */ 'use strict'; -var express = require('express'); -var bodyParser = require('body-parser'); -var BigNumber = require('bignumber.js'); -var fs = require('fs'); -var http = require('http'); -var https = require('https'); -var Web3 = require('web3'); -var web3 = new Web3(); -var argv = require('minimist')(process.argv.slice(2)); +const express = require('express'); +const bodyParser = require('body-parser'); +const BigNumber = require('bignumber.js'); +const fs = require('fs'); +const http = require('http'); +const https = require('https'); +const Web3 = require('web3'); +const web3 = new Web3(); +const argv = require('minimist')(process.argv.slice(2)); +const crypto = require('crypto'); +const path = require('path'); + +//Patch to support caching. +//map of form {name,address} +const cache = {}; + +function validateFile(filename){ + if ( path.isAbsolute(filename) && fs.existsSync(filename) ) + return filename + else if ( !path.isAbsolute(filename) && fs.existsSync("./" +filename)) + return path.resolve(filename) + return false +} + +function loadCache(batchInputFile) { + const NAME_LIST = JSON.parse(fs.readFileSync(batchInputFile, 'utf8')); + for (const entry of Object.entries(NAME_LIST)) { + cache[entry[0]] = entry[1] + } +} + + Object.getPrototypeOf(web3.eth).awaitConsensus = function(txhash, mined_cb) { var ethP = this; @@ -46,25 +69,45 @@ Object.getPrototypeOf(web3.eth).awaitConsensus = function(txhash, mined_cb) { } web3.setProvider(new web3.providers.HttpProvider('http://localhost:8545')); -var coinbase = web3.eth.coinbase; +const coinbase = web3.eth.coinbase; console.log(coinbase); -var balance = web3.eth.getBalance(coinbase); +let balance = web3.eth.getBalance(coinbase); console.log(balance.toString(10)); -var REG_FILE = __dirname + "/contract/registrar.out.json"; -var REG_ADDR_FILE = __dirname + "/contractAddress.txt"; -var NAME_VALIDATOR = new RegExp('^[a-z0-9-_]{3,32}$'); +const REG_FILE = __dirname + "/contract/registrar.out.json"; +const REG_ADDR_FILE = __dirname + "/contractAddress.txt"; +const NAME_VALIDATOR = new RegExp('^[a-z0-9-_]{3,32}$'); -var account; -var regAddress = "0xe53cb2ace8707526a5050bec7bcf979c57f8b44f"; -var regData; -var regContract; -var reg; +let account; +let regAddress = "0xe53cb2ace8707526a5050bec7bcf979c57f8b44f"; +let regData; +let regContract; +let reg; -const cache = {}; + +function loadNames(filename){ + console.log("The cache will be populated with the data from the export file!"); + const providedPath = String(argv['_'][0]); + const batchInputFile = validateFile(providedPath); + if(!batchInputFile){ + throw "File " + providedPath + " does not exist"; + } + else{ + loadCache(batchInputFile); + } +} + + +function verifySignature(name, _publickey, signature){ + const publicKey = new Buffer(_publickey, 'base64').toString('ascii') + const verifier = crypto.createVerify('RSA-SHA512'); + verifier.update(name); + const ver = verifier.verify(publicKey, signature,'base64'); + return ver; +} function unlockAccount() { - web3.personal.unlockAccount(coinbase, "toto"); + web3.personal.unlockAccount(coinbase, "apple123"); } function getRemainingGaz() { @@ -76,8 +119,8 @@ function waitForGaz(want, cb) { cb(); return; } - var timeout = function() { - var g = getRemainingGaz(); + const timeout = () => { + const g = getRemainingGaz(); if (g >= want) { //web3.miner.stop(); console.log("Mining finished ! Now having " + g + " gaz."); @@ -91,8 +134,8 @@ function waitForGaz(want, cb) { timeout(); } -function loadContract() { - fs.readFile(REG_ADDR_FILE, function(err, content) { +function loadContract(onContractLoaded) { + fs.readFile(REG_ADDR_FILE, (err, content) => { if (err) { console.log("Can't read contract address: " + err); } else { @@ -111,13 +154,13 @@ function loadContract() { console.log("Error getting contract code: " + error); if (!result || result == "0x") { console.log("Contract not found at " + regAddress); - initContract(); + initContract(onContractLoaded); } else { regContract.at(regAddress, function(err, result) { console.log("Contract found and loaded from " + regAddress); if(!err) { reg = result; - startServer(); + onContractLoaded(reg) } else { console.error("err: " + err); @@ -129,7 +172,7 @@ function loadContract() { }); } -function initContract() { +function initContract(onContractInitialized) { waitForGaz(3000000, function(){ regContract.new({ from: coinbase, data: '0x'+regData.evm.bytecode.object, @@ -142,7 +185,7 @@ function initContract() { regAddress = contract.address; fs.writeFileSync(REG_ADDR_FILE, regAddress); reg = contract; - startServer(); + onContractInitialized(); } } else { console.log(e); @@ -173,7 +216,7 @@ function parseString(s) { function formatAddress(address) { if (address) { - var s = address.trim(); + let s = address.trim(); try { if (s.startsWith("ring:")) s = s.substr(5); @@ -188,8 +231,8 @@ function formatAddress(address) { } function readCertificateChain(path) { - var cert = []; - var ca = []; + let cert = []; + const ca = []; fs.readFileSync(path, 'utf8').split("\n").forEach(function(line) { cert.push(line); if (line.match(/-END CERTIFICATE-/)) { @@ -200,9 +243,9 @@ function readCertificateChain(path) { return ca; } -function startServer() { +function startServer(result) { console.log("Starting web server"); - var app = express(); + const app = express(); app.disable('x-powered-by'); app.use(bodyParser.json()); app.use(function(req, res, next) { @@ -213,19 +256,94 @@ function startServer() { // Register name lookup handler app.get("/name/:name", function(req, http_res) { try { - reg.addr(formatName(req.params.name), function(err, res) { + reg.addr(formatName(req.params.name), function(err, res_addr) { + try { + if (err) + console.log("Name lookup error: " + err); + if (isHashZero(res_addr)) { + throw Error("name not registered"); + //http_res.status(404).end(JSON.stringify({"error": "name not registered"})); + } else { + reg.publickey(formatName(req.params.name), function(err, res_publickey) { + try { + if (err) + console.log("Name lookup error: " + err); + if (isHashZero(res_publickey)) { + http_res.end(JSON.stringify({"name": req.params.name, "addr": res_addr})); + } else { + reg.signature(formatName(req.params.name), function(err, res_signature) { + try { + if (err) + console.log("Name lookup error: " + err); + if (isHashZero(res_signature)) { + http_res.end(JSON.stringify({"name": req.params.name, "addr": res_addr})); + } else { + http_res.end(JSON.stringify({"name": req.params.name, "addr": res_addr, "publickey": res_publickey, "signature": res_signature })); + } + } catch (err) { + console.log("Name lookup exception: " + err); + http_res.status(500).end(JSON.stringify({"error": "server error"})); + } + }); + } + } catch (err) { + console.log("Name lookup exception: " + err); + http_res.status(500).end(JSON.stringify({"error": "server error"})); + } + }); + } + } catch (err) { + if(cache[req.params.name] != undefined){ + if(cache[req.params.name]['publickey'] && cache[req.params.name]['signature']){ + http_res.end(JSON.stringify({"name": req.params.name, "addr": cache[req.params.name]['addr'], "publickey": cache[req.params.name]['publickey'], "signature": cache[req.params.name]['signature']})); + } + else{ + http_res.end(JSON.stringify({"name": req.params.name, "addr": cache[req.params.name]['addr']})); + } + } + else{ + http_res.status(404).end(JSON.stringify({"error": "name not registered"})); + } + } + }); + } catch (err) { + console.log("Name lookup exception: " + err); + http_res.status(500).end(JSON.stringify({"error": "server error"})); + } + }); + + app.get("/name/:name/publickey", function(req, http_res) { + try { + reg.publickey(formatName(req.params.name), function(err, res) { try { if (err) console.log("Name lookup error: " + err); if (isHashZero(res)) { - const cachedAddress = cache[req.params.name]; - if (cachedAddress != undefined) { - http_res.end(JSON.stringify({"name": req.params.name,"addr": cachedAddress})); - } else { - http_res.status(404).end(JSON.stringify({"error": "name not registred"})); - } + http_res.status(404).end(JSON.stringify({"error": "name not registered"})); + } else { + http_res.end(JSON.stringify({"name": req.params.name, "publickey": res })); + } + } catch (err) { + console.log("Name lookup exception: " + err); + http_res.status(500).end(JSON.stringify({"error": "server error"})); + } + }); + } catch (err) { + console.log("Name lookup exception: " + err); + http_res.status(500).end(JSON.stringify({"error": "server error"})); + } + }); + + app.get("/name/:name/signature", function(req, http_res) { + try { + reg.signature(formatName(req.params.name), function(err, res) { + try { + if (err) + console.log("Name lookup error: " + err); + if (isHashZero(res)) { + http_res.status(404).end(JSON.stringify({"error": "name not registered"})); } else { - http_res.end(JSON.stringify({"name": req.params.name,"addr": res})); + http_res.end(JSON.stringify({"name": req.params.name, "signature": res })); } } catch (err) { console.log("Name lookup exception: " + err); @@ -246,15 +364,14 @@ function startServer() { if (err) console.log("Owner lookup error: " + err); if (isHashZero(res)) { - http_res.status(404).end(JSON.stringify({"error": "name not registred"})); + http_res.status(404).end(JSON.stringify({"error": "name not registered"})); } else { - http_res.end(JSON.stringify({"name": req.params.name,"owner": res})); + http_res.end(JSON.stringify({"name": req.params.name, "owner": res})); } } catch (err) { console.log("Owner lookup exception: " + err); http_res.status(500).end(JSON.stringify({"error": "server error"})); } - //http_res.end(JSON.stringify({"name": req.params.name,"owner": res})); }); } catch (err) { console.log("Owner lookup exception: " + err); @@ -279,7 +396,7 @@ function startServer() { if (name) http_res.end(JSON.stringify({"name": name})); else - http_res.status(404).end(JSON.stringify({"error": "address not registred"})); + http_res.status(404).end(JSON.stringify({"error": "address not registered"})); } catch (err) { console.log("Address lookup exception: " + err); http_res.status(500).end(JSON.stringify({"error": "server error"})); @@ -313,8 +430,36 @@ function startServer() { http_res.status(400).end(JSON.stringify({"success": false, "error": "invalid name"})); return; } + if(cache[req.params.name] == undefined){ + http_res.status(400).end(JSON.stringify({"success":false,"error": "name already registered"})); + return; + } + //Temporarily commented out for testing purposes. + //Backward compatibility patch to allow registrations without public keys: + let publickey; + let signature; + if(!req.body.publickey && !req.body.signature){ + publickey = 0; + signature = 0; + } + else{ + if (!req.body.publickey || req.body.publickey == "") { + http_res.status(400).end(JSON.stringify({"success": false, "error": "publickey not found or invalid"})); + return; + } + if (!req.body.signature || req.body.signature == "") { + http_res.status(400).end(JSON.stringify({"success": false, "error": "signature not found or invalid"})); + } + if(!verifySignature(req.params.name, req.body.publickey, req.body.signature)){ + http_res.status(401).end(JSON.stringify({"success": false, "error": "signature verification failed"})); + return; + } + else{ + publickey = req.body.publickey; + signature = req.body.signature; + } + } console.log("Got reg request (" + req.params.name + " -> " + addr + ") from " + req.body.owner); - reg.owner(req.params.name, function(err, owner) { if (owner == 0) { reg.name(addr, function(err, res) { @@ -328,7 +473,7 @@ function startServer() { } else { console.log("Remaing gaz: " + getRemainingGaz()); unlockAccount(); - reg.reserveFor.sendTransaction(formatName(req.params.name), req.body.owner, addr, { + reg.reserveFor.sendTransaction(formatName(req.params.name), req.body.owner, addr, publickey, signature, { from: coinbase, gas: 3000000 }, function(terr, reg_c) { @@ -336,16 +481,21 @@ function startServer() { console.log("Transaction error " + JSON.stringify(terr)); http_res.end(JSON.stringify(terr)); } else { + //Add the registration into the cache. + cache[req.params.name] = { + addr, + publickey, + signature + }; + //Now we continue with the sending of the transactions. console.log("Transaction sent " + reg_c); // Send answer as soon as the transaction is queued - cache[req.params.name] = addr; http_res.end(JSON.stringify({"success": true})); web3.eth.awaitConsensus(reg_c, function(error) { if (error) { console.log(error); return; } - delete cache[req.params.name]; console.log("Ended registration for " + req.params.name + " -> " + addr); }); } @@ -394,5 +544,8 @@ function startServer() { } } +if(argv['_'] != 0){ + loadNames(argv['_']); +} unlockAccount(); -loadContract(); +loadContract(startServer); diff --git a/start_eth_cluster.py b/start_eth_cluster.py deleted file mode 100644 index 44b8c0719c2750a31516ce4573c37cffabd36cbf..0000000000000000000000000000000000000000 --- a/start_eth_cluster.py +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/python3 -import os, sys -import time -import argparse -import subprocess as proc -import libtmux -import re - -parser = argparse.ArgumentParser(description='Launch Ethereum test nodes in screen') -parser.add_argument('--geth', '-e', default='geth', help='geth executable') -parser.add_argument('--start-port', '-p', type=int, default=4230, help='inital port') -parser.add_argument('--nodes', '-n', type=int, default=8, help='number of nodes') -parser.add_argument('--netid', '-id', type=int, default=4226, help='Ethereum network ID') -parser.add_argument('--datadir', '-d', help='Data directory') -parser.add_argument('--genesis', '-g', help='Genesis file', default='genesis.json') -args = parser.parse_args() - -data_dir = args.datadir -if not data_dir: - data_dir = os.path.expanduser("~/eth_net_{}/".format(args.netid)) -print("Using directory", data_dir) - -passwd = data_dir+'password' -try: - os.makedirs(data_dir) - with open(passwd,'w') as f: - f.write('toto') -except Exception as e: - pass - -print("Creating {} nodes.".format(args.nodes)) -for x in range(0, args.nodes): - ddir = data_dir + "node{}".format(x) - try: - os.makedirs(ddir) - cmd = '{} --datadir {} --networkid "{}" --identity "Node{}" init {}'.format(args.geth, ddir, args.netid, x, args.genesis) - print(cmd) - proc.run(cmd, shell=True) - cmd = '{} --datadir {} --networkid "{}" --identity "Node{}" --password {} account new'.format(args.geth, ddir, args.netid, x, passwd) - print(cmd) - proc.run(cmd, shell=True) - except Exception as e: - print(e) - -enode_finder = re.compile(b"enode://[0-9a-zA-Z]+@.+:\d+") -enodes = [] -try: - for x in range(0, args.nodes): - ddir = data_dir + "node{}".format(x) - cmd = '{} --datadir {} --networkid "{}" --identity "Node{}" --port {} console'.format(args.geth, ddir, args.netid, x, args.start_port+x) - print(cmd) - with proc.Popen(cmd, shell=True, stdout=proc.PIPE, stderr=proc.PIPE) as p: - while True: - l = p.stderr.readline() - if l != '': - res = enode_finder.search(l); - if res: - enodes.append(res.group(0)) - break - else: - break -except Exception as e: - print(e) - - -for enode in enodes: - print(enode) - -bootnodes = b",".join(enodes).decode().replace('[::]', '[::1]') - -ts = libtmux.Server() - -screen_name = "eth{}".format(args.start_port) -print("Launching {} nodes in tmux session {}.".format(args.nodes, screen_name)) -s = ts.new_session(screen_name) - -for x in range(0, args.nodes): - nm = "node{}".format(x) - w = s.new_window(nm) - p = w.attached_pane - ddir = data_dir + nm - suppargs = '' - #if x >= args.nodes/2: - # suppargs += '--minerthreads "1" --mine ' - if x == 0: - suppargs += '--rpc --rpcapi "personal,eth,net,web3" ' - cmd = '{} --datadir {} -networkid "{}" --identity "{}" --port {} --bootnodes "{}" {} --verbosity "3" console'.format(args.geth, ddir, args.netid, nm, args.start_port+x, bootnodes, suppargs) - print(cmd) - p.send_keys(cmd) - time.sleep(1)