Repository: telehash/blockname Branch: master Commit: 7c6d7a9d7a71 Files: 10 Total size: 23.7 KB Directory structure: gitextract_4qbqfp2g/ ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin/ │ ├── register.js │ ├── scan.js │ └── serve.js ├── lib/ │ └── index.js ├── notary.md └── package.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ bin/db/ # Logs logs *.log # Runtime data pids *.pid *.seed # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release # Dependency directory # Commenting this out is preferred by some people, see # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- node_modules # Users Environment Variables .lock-wscript ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - "0.10" ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2014 William Cotton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # blockname - A blockchain-backed DNS resolver This is a simple bitcoin and telehash based DNS resolver, using the blockchain as a backup cache for normal DNS resolution as well as to resolve alternative domains and custom DHT-based TLDs (completely distributed, no registrars, root servers, or central authorities). Simply publish your own hostname as a valid `OP_RETURN` output on *any* transaction with the text format `*!host.name.com11223344` (valid lower case text hostname followed by a fixed 8-char hex IP address), these are called `hint` transactions and the first byte is always the star character (`*`). The hints can be registered with any bitcoin wallet software that can include an `OP_RETURN` output on a transaction. The blockname resolver is a traditional DNS cache and recursive resolver, it will attempt to resolve all queries via regular DNS first and only when they fail will it use any names that come from the blockchain-based hints. In this mode blockname will always act as a backup for any existing valid DNS names and only provides additional resolution for unregistered domains or unsupported TLDs. In the background the resolver will continuously index all newly broadcast transactions that have a valid hints (any `OP_RETURN` starting with a `*`), storing only the unique hints that have the largest values associated with them. The value of the hint's own output (the "burned" value in satoshis) must be larger for the new hint to replace a previous one of the same name. A custom TLD is formed by designated public blockname resolvers advertising their existence to each other and building a distributed hashtable (DHT) index for a TLD from those advertisements. The DHT index is then used to dynamically resolve any names with that TLD, allowing for ephemeral and alternative uses on a custom TLD that do not require a transaction per name or traditional DNS registration. ## Status This project is at an early stage of development yet and actively evolving. It is [currently working](http://testnet.coinsecrets.org/?to=322562.000001) on testnet ([domain hint](https://www.blocktrail.com/tBTC/tx/d1bb941d7efc1fc33920a9ac48dc1e46bd1be0ebaadb29768d28aeda1736c1a3) and [host hint](https://www.blocktrail.com/tBTC/tx/3cf995487dacff844ff3c000f1d57032731e8aa6d5fb2de98b90ee14c60197b9)), and being tested/developed for the [main blockchain](https://www.blocktrail.com/BTC/tx/823d02d2689bdb1430faddc4a6c57fc0b7be23e1e56ee686c92f300d67e51390#tx_messages). These commands are working but expect them to change: ``` git clone https://github.com/quartzjer/blockname.git cd blockname npm install ``` Start a local DNS resolver (defaults to port `8053`) ``` node bin/serve.js ``` Start a process to sync and monitor the transactions on the (testnet) blockchain: ``` node bin/scan.js ``` Register your own hint on the blockchain, passing the hostname and an IP to resolve any `A` queries, or a domain and IP:port of a nameserver that will resolve that whole domain. Uses a testnet faucet service by default currently, will generate a temporary address to send funds to (run command w/ no args to see options) ``` node bin/register.js "somename.tld" 12.34.56.78 ``` Now do a test resolution to the local cache server, it will check normal DNS first, then fallback to any indexed hints from the blockchain: ``` dig somename.tld @127.0.0.1 -p 8053 ``` ## Plans After some more testing and docs, this will default to mainnet and become a `blocknamed` DNS resolver service and `blockname` registration command that anyone can `npm install -g blockname`. There will be a list of public blockname resolvers that can be used by anyone and a web-based registration tool and a chart of top hints in the blockchain. Explore using types of secondary "confirmation" hints to enable self-forming distributed organizations a way to help reduce potential abuse of the simple value based priority? # Hint Types ## Host Hints `*!` A host hint is a direct mapping of an exact hostname to an IP address, an answer is returned immediately to any query with the given IP. Any matching domain or TLD hints are authorative and checked first, hostname hints are only used when there is no other answer. * Any OP_RETURN starting with `*!` * followed by up to 30 valid domain name characters with any number of labels * followed by a required 8 characters of the IPv4 address in hex Examples: * `test.domain.tld` A `192.168.0.1` => hint `*!test.domain.tldc0a80001` * `test.name` A `1.2.3.4` => hint `*!test.name01020304` * `some.host.jeremie.com` A `208.68.163.251` => hint `*!some.host.jeremie.comd044a3fb` ## Domain Hints `*.` Domain hints are used to match one or more given queries to a name server IP and port. Any DNS query including or matching the suffix/domain will be forwarded to the hint's IP:port and any answers returned verbatim and cached. A domain hint is only matched if there is no TLD answer. * any OP_RETURN starting with `*.` * followed by up to 26 valid [domain name](http://en.wikipedia.org/wiki/Domain_name) characters that must include two labels (name.tld) * followed by a required 8 characters that are always the IPv4 address octets hex encoded, this address is used as the dns server to forward the query to * followed by a required 4 characters that are the port of the DNS server in hex (network byte order uint16_t) Examples: * `domain.tld` NS `192.168.0.1:53` => hint `*.domain.tldc0a800010035` * `test.name` NS `1.2.3.4:1286` => hint `*.test.name010203040506` * `jeremie.com` NS `208.68.163.251:53` => hint `*.jeremie.comd044a3fb0035` ## Name Authority Hints `*+` > work-in-progress, very rough draft A Name Authority is broadcast in a TX: * Any OP_RETURN starting with `*+` * followed by the hostname of the NA * followed by 8 hex characters, the new [Notary ID](notary.md) The NA then also Stamps this transaction, which must be published/verified out of band by the hostname (via TLS, DNS-SEC, or NA-chaining). Once the NA hint Stamp is verified, any subsequent Stamps are trusted from this NA. ## TLD Hints `*#` A TLD hint will match any query with the given root label and send a query to the DHT for that label. The hints start with a `*#` followed by the TLD label characters, then separated with a `.` from one or more hex characters of the node's location in the DHT and its IP:port. TLDs are always checked first after there is no traditional DNS answer, before checking for any domain or host hints. When creating a new blockname based TLD, care should be taken not to create conflicts with any [existing or proposed](http://en.wikipedia.org/wiki/List_of_Internet_top-level_domains) names. * `*#tld.dht010203040506` * DHT is the hex prefix of hashname at the IP:port * by default include as much hex as possible, only designated would elect shorter * TLD caretakers must monitor advertised hints for abuse * DHT participating resolvers must be customized to mesh (know how to answer queries) All blockname resolvers will process TLD hints and attempt to keep a connection open to at least eight of the shortest DHT prefix hints for each unique TLD. When any query comes in matching that TLD, the name will be hashed and sent to the closest two connected hashnames which will internally process/route the query and return an answer if any. A resolver that is helping maintain a TLD must process all of its matching hints, attempting to keep connections open to a minimum number of peers in each bucket closest to its own hashname. The DHT is always seeded only by peers with valid hints, and each bucket is prioritzed by the value of the hints in that bucket. Participating resolvers may have custom internal query processing and routing logic per TLD, these customizations can only be validated by other peers in that TLD ensuring that the highest priority hints are behaving correctly. > WIP, merging [dotPublic](https://github.com/telehash/dotPublic) into here ### Well-known blockname TLDs #### `.hashname` This TLD is dedicated to resolving [hashnames](https://github.com/telehash/telehash.org/tree/master/v3/hashname) for any [telehash](http://telehash.org) based service to associate itself publicly with a known stable network location. They should only be used for services that are intended to be public (web servers, etc), private devices should never be published in an anonymous DHT. * `4w0fh69ad6d1xhncwwd1020tqnhqm4y5zbdmtqdk7d3v36qk6wbg.hashname` All hashname lookups are internally verified against the returned IP and port with a handshake to ensure authenticity before returning their information to any queries. #### `.btc` Any bitcoin address that is a public key (starts with `1`) can be resolved and verified under the `.btc` TLD. While the base58 string encoding of a bitcoin address is regularly used and would be optimal for mapping to a special TLD, normal DNS is case-insensitive and some DNS tools may not support the case-sensitive base58 encoding. In practice most DNS resolution libraries will just pass the queried hostname verbatim through to the resolver/server and the address will be preserved, so using the base58check address often works when mapping from normal DNS. When possible, the address may be converted from base58 to base32 before sending to a blockname resolver via DNS. * `16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM.btc` #### Others > TODO: decide/document on support for .onion, .bit, etc alternative TLDs, write a guide for creating a new custom TLD ## Thanks Thanks to help from [William Cotton](https://github.com/williamcotton/blockcast). ================================================ FILE: bin/register.js ================================================ var bitcoin = require('bitcoinjs-lib'); var opret = require('raw-op-return'); var ip = require('ip'); var level = require('level-party'); var yargs = require('yargs') .describe('db','hint storage db directory') .describe('key','WIF format secret key to use for the registration transaction') .boolean('test').describe('test','use a testnet faucet to fund the registration').default('test',true) .usage('Usage: $0 "domain" 1.2.3.4:5678 [satoshis] [refundto]') .demand(2); var argv = yargs.argv; var domain = argv._[0]; var ipp = argv._[1].split(':'); try { var server = ip.toBuffer(ipp[0]); }catch(E){} if(!server || server.length != 4) return console.error('bad ip address',argv._[1]); // check for optional port to register a NS hint if(ipp.length > 1) { var port = parseInt(ipp[1]) || 53; var ns = new Buffer(2); ns.writeUInt16BE(port,0); var hint = '*.'+domain+server.toString('hex')+ns.toString('hex'); }else{ var hint = '*!'+domain+server.toString('hex'); } if(hint.length > 40) return console.error('hint too large, name must be <32 chars:',hint); var network = argv.test?bitcoin.networks.testnet:bitcoin.networks.bitcoin; var key = argv.key ? bitcoin.ECKey.fromWIF(argv.key) : bitcoin.ECKey.makeRandom(); var address = key.pub.getAddress(network).toString(); var dbdir = argv.db || (__dirname + '/db'); var db = level(dbdir, { encoding: 'json' }); var helloblock = require('helloblock-js')({network:argv.test?'testnet':'mainnet'}); // optional value to spend on registration var value = parseInt(argv._[2]) || 1000; var refund = argv._[3] || address; console.log('using private key (WIF)',key.toWIF(),argv.test); console.log('public address requiring funds',address); console.log('registering hint `%s` to %s (OP_RETURN `%s`)', domain, ip.toString(server), hint); console.log('spending %d and sending balance to %s',value,refund); // test mode, we use a faucet to get funds first if(argv.test) { helloblock.faucet.withdraw(address, 10000, function(err, res, ret) { if(err) return console.error('faucet withdrawl failed',err); unspent(); }); }else{ // live mode, start looking for unspents unspent(); } // loop until we find enough unspents for the given address function unspent(){ helloblock.addresses.getUnspents(address, function(err, res, unspents) { if(err) return console.error('fetching unspent failed',err); var total = 0; unspents.forEach(function(utx){ total += utx.value; }); if(total < value) { console.log('not enough funds found yet, waiting...',total,value); return setTimeout(unspent,10*1000); } register(key,refund,unspents,hint); }); } // actually do the registration transaction function register(from, to, sources, hint){ console.log('performing registration'); var signTransaction = function(tx, callback) { tx.sign(0, from); callback(false, tx); }; var propagateTransaction = function(tx, callback) { helloblock.transactions.propagate(tx, function(err, res, body) { callback(err, res); }); }; // use the OP_RETURN module opret.post({ stringData: hint, address: to, fee: network.estimateFee, unspentOutputs: sources, propagateTransaction: propagateTransaction, signTransaction: signTransaction }, function(error, postedTx) { if(error) return console.error('registration error',error); console.log('registered hint', postedTx.txHash); db.put(domain,{ip:ip.toString(server),v:postedTx.totalInputsValue},function(){ process.exit(); }); }); }; ================================================ FILE: bin/scan.js ================================================ var argv = require('yargs') .boolean('test').describe('test', 'use testnet').default('test',true) .describe('block','starting block') .describe('db','hint storage db directory') .boolean('list').describe('list','print a list of all hints in the local db') .argv; // start from a default recent block on the right network var id = argv.block; if(!id) id = argv.test?322560:342656; var network = argv.test?'testnet':'mainnet'; var dbdir = argv.db || (__dirname + '/db'); var helloblock = require('helloblock-js')({network:network}); var opret = require('raw-op-return'); var ip = require('ip'); var level = require('level-party'); var db = level(dbdir, { encoding: 'json' }); // print out all hints if(argv.list) { var count = 0; db.createReadStream() .on('data', function (data) { count++; console.log(data.key, data.value) }) .on('end', function () { console.log(count+' hints in '+dbdir); process.exit(); }); return; } console.log('starting to scan for hints from',network,'at block',id,'into',dbdir); function setHint(name, hint) { console.log('checking hint',name); db.get(name, function(err, existing){ if(existing) { if(hint.ip == existing.ip && hint.v == existing.v) return; // ignore duplicate if(hint.v < existing.v) return console.log('existing better hint',name,existing); } console.log('saving new hint', name, hint); db.put(name, hint); }); } function getBlock() { helloblock.blocks.getTransactions(id , {limit: 1000}, function(err, res, transactions){ if(!Array.isArray(transactions) || transactions.length == 0) { console.log('waiting for block',id); setTimeout(getBlock,10*1000); return; } console.log(id,transactions.length); id++; setTimeout(getBlock,100); transactions.forEach(function(tx){ opret.scan(tx, function(err, dtx) { if(!dtx || !dtx.data) return; var opreturn = dtx.data; var value = dtx.output.value; // first character '*' if(opreturn.length < 10 || opreturn[0] != 42) return; var type = opreturn[1]; // second character '.' is domain hint ip+port if(type == 46) { // include the . in the name for the cache var domain = opreturn.slice(1,opreturn.length-12).toString(); var iphex = opreturn.slice(opreturn.length-12, opreturn.length-4).toString(); var ipbuf = new Buffer(iphex,'hex'); if(ipbuf.length != 4) return console.log('invalid ip hex'); var server = ip.toString(ipbuf); var porthex = opreturn.slice(opreturn.length-4).toString(); var portbuf = new Buffer(porthex,'hex'); if(portbuf.length != 2) return console.log('invalid port hex'); var port = portbuf.readUInt16BE(0); if(!port) return console.log('invalid port 0'); var hint = {ip:server, port:port, v:value}; return setHint(domain, hint); } // second character is a '!' hostname hint, ip only if(type == 33) { var host = opreturn.slice(2,opreturn.length-8).toString(); var iphex = opreturn.slice(opreturn.length-8).toString(); var ipbuf = new Buffer(iphex,'hex'); if(ipbuf.length != 4) return console.log('invalid ip hex'); var server = ip.toString(ipbuf); var hint = {ip:server, v:value}; return setHint(host, hint); } console.log('found unknown hint',type,opreturn.slice(2).toString('hex')); }); }) }); } getBlock() ================================================ FILE: bin/serve.js ================================================ var argv = require('yargs') .describe('port','listen port').default('port',8053) .describe('db','hint storage db directory') .describe('ignore','ip address to ignore any results to') .argv; var dbdir = argv.db || (__dirname + '/db'); var ignore = {}; if(argv.ignore) ignore[argv.ignore] = true; var dns = require('native-dns'); var server = dns.createServer(); var level = require('level-party'); var db = level(dbdir, { encoding: 'json' }); console.log('resolving dns requests at',argv.port,'with hints from',dbdir); // check for domain match first function getDomain(domain, cbHint) { db.get('.'+domain, function(err, hint){ cbHint(err, hint && hint.port); }); } // find any hint function getHint(name, cbHint) { // paranoid sanity name = name.toLowerCase(); if(name.substr(name.length-1) == '.') name = name.substr(0,name.length-1); // first try to get any domain matching one var labels = name.split('.'); var domain = labels.join(labels.slice(labels.length-2),'.'); getDomain(domain, function(err, hint){ if(hint) return cbHint(false, hint); // fallback get any host match db.get(name, cbHint); }); } server.on('request', function (request, response) { if(!Array.isArray(request.question) || request.question.length == 0) return; if(!dns.platform.name_servers.length) return console.log('no local name servers?'); var q = request.question[0]; // first try to resolve it normally var req = dns.Request({ question: q, server: dns.platform.name_servers[0], timeout: 2000 }); var ok = false; req.on('message', function (err, answer) { if(err || answer.answer.length == 0) return; answer.answer.forEach(function (a) { if(ignore[a.address]) return; response.answer.push(a); ok = true; }); if(ok) response.send(); }); req.on('end', function () { if(ok) return console.log('ok\t',q.name); // now check for a hint to use as the nameserver getHint(q.name, function(err, hint){ if(err || !hint || !hint.ip) { console.log('n/a\t',q.name); return response.send(); } // any host hints don't have a port if(!hint.port) { console.log('host\t',q.name,hint); response.answer.push(dns.A({name: q.name, address: hint.ip, ttl: 60})); response.send(); return; } console.log('domain\t',q.name,hint); var req = dns.Request({ question: q, server: { address: hint.ip, port: hint.port, type: 'udp' }, timeout: 2000 }); req.on('message', function (err, answer) { if(err || answer.answer.length == 0) return; answer.answer.forEach(function (a) { response.answer.push(a); }); }); req.on('end', function () { response.send(); }); req.send(); }); }); req.send(); }); server.on('error', function (err, buff, req, res) { console.log(err.stack); }); server.serve(argv.port); // check if the local resolver responsds to any domain already and ignore it dns.resolve('globcheck.tld',function(err,addresses){ if(Array.isArray(addresses)) addresses.forEach(function(ip){ if(ignore[ip]) return; console.log('ignoring a catch-all IP',ip); ignore[ip] = true; }); }); ================================================ FILE: lib/index.js ================================================ // TODO, migrate code from ../bin/*.js to a library here ================================================ FILE: notary.md ================================================ # Bitcoin Notary - Anonymous, Distributed, & Simple > work in progress The goal is to provide a very small and simple technique for any (anonymous) entity to "notarize" any bitcoin transaction, independent of the actual transaction itself and without requiring any public key technology. Terminology: * Notary - a sequential source of assertions / revocations of any txid (Stamps) * Stamp - a single notarization published in any transaction as an OP_RETURN A Notary is formed by always keeping/generating a new secret token that is used to encrypt the last Stamp, when a new Stamp is published it is encrpyed with a new secret token for the next one. Any other entity can establish trust starting from any Stamp and verify subsequent Stamps in the blockchain created from it by the Notary. Stamps can only be validated in order since the token links the validation "forward", the last one is always dangling and can't be verified/decrypted until the next one is known. A Stamp is a 40-byte binary OP_RETURN containing: * 4 byte Notary ID * 16 byte token * 16 byte ciphertext * 4 byte MAC The ciphertext is generated using ChaCha20, the cipher key is the SHA-256 digest of the *next* Stamp's token, and the nonce is the 4 byte Notary ID + 4 byte MAC. The encrypted 16 bytes are the first half of an existing bitcoin transaction id. The MAC is the SipHash digest output of the Notary ID, key, and the full bitcoin transaction id (52 bytes) using the same cipher key. Positive values output to the OP_RETURN are an `assertion`, 0 values are a `revocation`. ================================================ FILE: package.json ================================================ { "name": "blockname", "version": "0.0.1", "description": "A simple bitcoin-based DNS cache", "main": "./lib/index.js", "scripts": { }, "repository": { "type": "git", "url": "https://github.com/quartzjer/blockname.git" }, "keywords": [ "bitcoin", "blockchain", "dns", "decentralized", "crypto", "cryptocurrency" ], "author": "Jeremie Miller", "license": "MIT", "bugs": { "url": "https://github.com/quartzjer/blockname/issues" }, "homepage": "https://github.com/quartzjer/blockname", "devDependencies": { }, "dependencies": { "bitcoinjs-lib": "^1.2.0", "helloblock-js": "^0.2.5", "ip": "^0.3.2", "level-party": "^1.0.1", "native-dns": "^0.7.0", "raw-op-return": "git+https://github.com/quartzjer/raw-op-return.git", "yargs": "^1.3.3" } }