Commit 24aa2a0c authored by Félix  Sidokhine's avatar Félix Sidokhine

nameservice: public-key and signature now stored on blockchain

The public-key and signature can now be POSTed at time of registration
These are validated by server before posting to blockchain, and can be
retrieved by users for validation on their local machines as well

Added appropriate HTTP codes as well

Modified contract to store and retrieve these values and various scripts
(server, batch reading/writing) to handle these new values

Change-Id: Iddcaa00ab0d7068cea6d578489eacb89e8354b7c
parent 232fe05c
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;
......
......@@ -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);
#!/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