[
  {
    "path": ".gitignore",
    "content": "bin/db/\n\n# Logs\nlogs\n*.log\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directory\n# Commenting this out is preferred by some people, see\n# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-\nnode_modules\n\n# Users Environment Variables\n.lock-wscript\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nnode_js:\n  - \"0.10\"\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014 William Cotton\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "README.md",
    "content": "# blockname - A blockchain-backed DNS resolver\n\nThis 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).\n\nSimply 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.\n\nThe 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.\n\nIn 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.\n\nA 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.\n\n## Status\n\nThis project is at an early stage of development yet and actively evolving.\n\nIt 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).\n\nThese commands are working but expect them to change:\n\n```\ngit clone https://github.com/quartzjer/blockname.git\ncd blockname\nnpm install\n```\n\nStart a local DNS resolver (defaults to port `8053`)\n\n```\nnode bin/serve.js\n```\n\nStart a process to sync and monitor the transactions on the (testnet) blockchain:\n\n```\nnode bin/scan.js\n```\n\nRegister 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)\n\n```\nnode bin/register.js \"somename.tld\" 12.34.56.78\n```\n\nNow do a test resolution to the local cache server, it will check normal DNS first, then fallback to any indexed hints from the blockchain:\n\n```\ndig somename.tld @127.0.0.1 -p 8053\n```\n\n## Plans\n\nAfter 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`.\n\nThere 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.\n\nExplore 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?\n\n# Hint Types\n\n## Host Hints `*!`\n\nA 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.\n\nAny matching domain or TLD hints are authorative and checked first, hostname hints are only used when there is no other answer.\n\n* Any OP_RETURN starting with `*!`\n* followed by up to 30 valid domain name characters with any number of labels\n* followed by a required 8 characters of the IPv4 address in hex\n\nExamples:\n\n* `test.domain.tld` A `192.168.0.1` => hint `*!test.domain.tldc0a80001`\n* `test.name` A `1.2.3.4` => hint `*!test.name01020304`\n* `some.host.jeremie.com` A `208.68.163.251` => hint `*!some.host.jeremie.comd044a3fb`\n\n## Domain Hints `*.`\n\nDomain 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.\n\nA domain hint is only matched if there is no TLD answer.\n\n* any OP_RETURN starting with `*.`\n* followed by up to 26 valid [domain name](http://en.wikipedia.org/wiki/Domain_name) characters that must include two labels (name.tld)\n* 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\n* followed by a required 4 characters that are the port of the DNS server in hex (network byte order uint16_t)\n\nExamples:\n\n* `domain.tld` NS `192.168.0.1:53` => hint `*.domain.tldc0a800010035`\n* `test.name` NS `1.2.3.4:1286` => hint `*.test.name010203040506`\n* `jeremie.com` NS `208.68.163.251:53` => hint `*.jeremie.comd044a3fb0035`\n\n\n## Name Authority Hints `*+`\n\n> work-in-progress, very rough draft\n\nA Name Authority is broadcast in a TX:\n\n* Any OP_RETURN starting with `*+`\n* followed by the hostname of the NA\n* followed by 8 hex characters, the new [Notary ID](notary.md)\n\nThe 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.\n\n## TLD Hints `*#`\n\nA 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.\n\nTLDs 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.\n\n* `*#tld.dht010203040506`\n* DHT is the hex prefix of hashname at the IP:port\n* by default include as much hex as possible, only designated would elect shorter\n* TLD caretakers must monitor advertised hints for abuse\n* DHT participating resolvers must be customized to mesh (know how to answer queries)\n\nAll 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.\n\nA 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.\n\nParticipating 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.\n\n> WIP, merging [dotPublic](https://github.com/telehash/dotPublic) into here\n\n### Well-known blockname TLDs\n\n#### `.hashname`\n\nThis 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.\n\n* `4w0fh69ad6d1xhncwwd1020tqnhqm4y5zbdmtqdk7d3v36qk6wbg.hashname`\n\nAll hashname lookups are internally verified against the returned IP and port with a handshake to ensure authenticity before returning their information to any queries.\n\n#### `.btc`\n\nAny bitcoin address that is a public key (starts with `1`) can be resolved and verified under the `.btc` TLD.\n\nWhile 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.\n\n* `16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM.btc`\n\n#### Others\n\n> TODO: decide/document on support for .onion, .bit, etc alternative TLDs, write a guide for creating a new custom TLD\n\n## Thanks\n\nThanks to help from [William Cotton](https://github.com/williamcotton/blockcast).\n"
  },
  {
    "path": "bin/register.js",
    "content": "var bitcoin = require('bitcoinjs-lib');\nvar opret = require('raw-op-return');\nvar ip = require('ip');\nvar level = require('level-party');\n\nvar yargs = require('yargs')\n  .describe('db','hint storage db directory')\n  .describe('key','WIF format secret key to use for the registration transaction')\n  .boolean('test').describe('test','use a testnet faucet to fund the registration').default('test',true)\n  .usage('Usage: $0 \"domain\" 1.2.3.4:5678 [satoshis] [refundto]')\n  .demand(2);\nvar argv = yargs.argv;\n\nvar domain = argv._[0];\nvar ipp = argv._[1].split(':');\ntry { var server = ip.toBuffer(ipp[0]); }catch(E){}\nif(!server || server.length != 4) return console.error('bad ip address',argv._[1]);\n\n// check for optional port to register a NS hint\nif(ipp.length > 1)\n{\n  var port = parseInt(ipp[1]) || 53;\n  var ns = new Buffer(2);\n  ns.writeUInt16BE(port,0);\n  var hint = '*.'+domain+server.toString('hex')+ns.toString('hex');\n}else{\n  var hint = '*!'+domain+server.toString('hex');\n}\n\nif(hint.length > 40) return console.error('hint too large, name must be <32 chars:',hint);\n\nvar network = argv.test?bitcoin.networks.testnet:bitcoin.networks.bitcoin;\nvar key = argv.key ? bitcoin.ECKey.fromWIF(argv.key) : bitcoin.ECKey.makeRandom();\nvar address = key.pub.getAddress(network).toString();\nvar dbdir = argv.db || (__dirname + '/db');\nvar db = level(dbdir, { encoding: 'json' });\nvar helloblock = require('helloblock-js')({network:argv.test?'testnet':'mainnet'});\n\n// optional value to spend on registration\nvar value = parseInt(argv._[2]) || 1000;\nvar refund = argv._[3] || address;\n\nconsole.log('using private key (WIF)',key.toWIF(),argv.test);\nconsole.log('public address requiring funds',address);\nconsole.log('registering hint `%s` to %s (OP_RETURN `%s`)', domain, ip.toString(server), hint);\nconsole.log('spending %d and sending balance to %s',value,refund);\n\n\n// test mode, we use a faucet to get funds first\nif(argv.test)\n{\n  helloblock.faucet.withdraw(address, 10000, function(err, res, ret) {\n    if(err) return console.error('faucet withdrawl failed',err);\n    unspent();\n  });\n  \n}else{\n  // live mode, start looking for unspents\n  unspent();\n}\n\n\n// loop until we find enough unspents for the given address\nfunction unspent(){\n  helloblock.addresses.getUnspents(address, function(err, res, unspents) {\n    if(err) return console.error('fetching unspent failed',err);\n    var total = 0;\n    unspents.forEach(function(utx){\n      total += utx.value;\n    });\n    if(total < value)\n    {\n      console.log('not enough funds found yet, waiting...',total,value);\n      return setTimeout(unspent,10*1000);\n    }\n    register(key,refund,unspents,hint);\n  });\n  \n}\n\n\n// actually do the registration transaction\nfunction register(from, to, sources, hint){\n  console.log('performing registration');\n\n  var signTransaction = function(tx, callback) {\n    tx.sign(0, from); \n    callback(false, tx);\n  };\n  \n  var propagateTransaction = function(tx, callback) {\n    helloblock.transactions.propagate(tx, function(err, res, body) {\n      callback(err, res);\n    });\n  };\n  \n  // use the OP_RETURN module\n  opret.post({\n    stringData: hint,\n    address: to,\n    fee: network.estimateFee,\n    unspentOutputs: sources,\n    propagateTransaction: propagateTransaction,\n    signTransaction: signTransaction\n  }, function(error, postedTx) {\n    if(error) return console.error('registration error',error);\n    console.log('registered hint', postedTx.txHash);\n    db.put(domain,{ip:ip.toString(server),v:postedTx.totalInputsValue},function(){\n      process.exit();\n    });\n  });\n\n};\n\n"
  },
  {
    "path": "bin/scan.js",
    "content": "var argv = require('yargs')\n  .boolean('test').describe('test', 'use testnet').default('test',true)\n  .describe('block','starting block')\n  .describe('db','hint storage db directory')\n  .boolean('list').describe('list','print a list of all hints in the local db')\n  .argv;\n\n// start from a default recent block on the right network\nvar id = argv.block;\nif(!id) id = argv.test?322560:342656;\nvar network = argv.test?'testnet':'mainnet';\nvar dbdir = argv.db || (__dirname + '/db');\n\n\nvar helloblock = require('helloblock-js')({network:network});\nvar opret = require('raw-op-return');\nvar ip = require('ip');\nvar level = require('level-party');\nvar db = level(dbdir, { encoding: 'json' });\n\n// print out all hints\nif(argv.list)\n{\n  var count = 0;\n  db.createReadStream()\n  .on('data', function (data) {\n    count++;\n    console.log(data.key, data.value)\n  })\n  .on('end', function () {\n    console.log(count+' hints in '+dbdir);\n    process.exit();\n  });\n  return;\n}\n\nconsole.log('starting to scan for hints from',network,'at block',id,'into',dbdir);\n\nfunction setHint(name, hint)\n{\n  console.log('checking hint',name);\n\n  db.get(name, function(err, existing){\n    if(existing)\n    {\n      if(hint.ip == existing.ip && hint.v == existing.v) return; // ignore duplicate\n      if(hint.v < existing.v) return console.log('existing better hint',name,existing);\n    }\n    console.log('saving new hint', name, hint);\n    db.put(name, hint);\n  });\n  \n}\n\nfunction getBlock()\n{\n  helloblock.blocks.getTransactions(id , {limit: 1000}, function(err, res, transactions){\n    if(!Array.isArray(transactions) || transactions.length == 0)\n    {\n      console.log('waiting for block',id);\n      setTimeout(getBlock,10*1000);\n      return;\n    }\n    console.log(id,transactions.length);\n    id++;\n    setTimeout(getBlock,100);\n    \n    transactions.forEach(function(tx){\n      opret.scan(tx, function(err, dtx) {\n        if(!dtx || !dtx.data) return;\n        var opreturn = dtx.data;\n        var value = dtx.output.value;\n        \n        // first character '*'\n        if(opreturn.length < 10 || opreturn[0] != 42) return;\n\n        var type = opreturn[1];\n        \n        // second character '.' is domain hint ip+port\n        if(type == 46)\n        {\n          // include the . in the name for the cache\n          var domain = opreturn.slice(1,opreturn.length-12).toString();\n          var iphex = opreturn.slice(opreturn.length-12, opreturn.length-4).toString();\n          var ipbuf = new Buffer(iphex,'hex');\n          if(ipbuf.length != 4) return console.log('invalid ip hex');\n          var server = ip.toString(ipbuf);\n          var porthex = opreturn.slice(opreturn.length-4).toString();\n          var portbuf = new Buffer(porthex,'hex');\n          if(portbuf.length != 2) return console.log('invalid port hex');\n          var port = portbuf.readUInt16BE(0);\n          if(!port) return console.log('invalid port 0');\n          \n          var hint = {ip:server, port:port, v:value};\n          return setHint(domain, hint);\n        }\n        \n        // second character is a '!' hostname hint, ip only\n        if(type == 33)\n        {\n          var host = opreturn.slice(2,opreturn.length-8).toString();\n          var iphex = opreturn.slice(opreturn.length-8).toString();\n          var ipbuf = new Buffer(iphex,'hex');\n          if(ipbuf.length != 4) return console.log('invalid ip hex');\n          var server = ip.toString(ipbuf);\n          \n          var hint = {ip:server, v:value};\n          return setHint(host, hint);\n        }\n\n        console.log('found unknown hint',type,opreturn.slice(2).toString('hex'));\n\n      });\n    })\n  });  \n}\n\ngetBlock()"
  },
  {
    "path": "bin/serve.js",
    "content": "var argv = require('yargs')\n  .describe('port','listen port').default('port',8053)\n  .describe('db','hint storage db directory')\n  .describe('ignore','ip address to ignore any results to')\n  .argv;\n\nvar dbdir = argv.db || (__dirname + '/db');\nvar ignore = {};\nif(argv.ignore) ignore[argv.ignore] = true;\n\nvar dns = require('native-dns');\nvar server = dns.createServer();\nvar level = require('level-party');\nvar db = level(dbdir, { encoding: 'json' });\n\nconsole.log('resolving dns requests at',argv.port,'with hints from',dbdir);\n\n// check for domain match first\nfunction getDomain(domain, cbHint)\n{\n  db.get('.'+domain, function(err, hint){\n    cbHint(err, hint && hint.port);\n  });\n}\n\n// find any hint\nfunction getHint(name, cbHint)\n{\n  // paranoid sanity\n  name = name.toLowerCase();\n  if(name.substr(name.length-1) == '.') name = name.substr(0,name.length-1);\n\n  // first try to get any domain matching one\n  var labels = name.split('.');\n  var domain = labels.join(labels.slice(labels.length-2),'.');\n  getDomain(domain, function(err, hint){\n    if(hint) return cbHint(false, hint);\n    \n    // fallback get any host match\n    db.get(name, cbHint);\n  });\n  \n}\n\nserver.on('request', function (request, response) {\n  if(!Array.isArray(request.question) || request.question.length == 0) return;\n  if(!dns.platform.name_servers.length) return console.log('no local name servers?');\n  var q = request.question[0];\n\n  // first try to resolve it normally\n  var req = dns.Request({\n    question: q,\n    server: dns.platform.name_servers[0],\n    timeout: 2000\n  });\n\n  var ok = false;\n  req.on('message', function (err, answer) {\n    if(err || answer.answer.length == 0) return;\n    answer.answer.forEach(function (a) {\n      if(ignore[a.address]) return;\n      response.answer.push(a);\n      ok = true;\n    });\n    if(ok) response.send();\n  });\n\n  req.on('end', function () {\n    if(ok) return console.log('ok\\t',q.name);\n\n    // now check for a hint to use as the nameserver\n    getHint(q.name, function(err, hint){\n      if(err || !hint || !hint.ip)\n      {\n        console.log('n/a\\t',q.name);\n        return response.send();\n      }\n\n      \n      // any host hints don't have a port\n      if(!hint.port)\n      {\n        console.log('host\\t',q.name,hint);\n        response.answer.push(dns.A({name: q.name, address: hint.ip, ttl: 60}));\n        response.send();\n        return;\n      }\n\n      console.log('domain\\t',q.name,hint);\n      var req = dns.Request({\n        question: q,\n        server: { address: hint.ip, port: hint.port, type: 'udp' },\n        timeout: 2000\n      });\n\n      req.on('message', function (err, answer) {\n        if(err || answer.answer.length == 0) return;\n        answer.answer.forEach(function (a) {\n          response.answer.push(a);\n        });\n      });\n\n      req.on('end', function () {\n        response.send();\n      });\n      \n      req.send();\n    });\n\n  });\n\n  req.send();\n});\n\nserver.on('error', function (err, buff, req, res) {\n  console.log(err.stack);\n});\n\nserver.serve(argv.port);\n\n// check if the local resolver responsds to any domain already and ignore it\ndns.resolve('globcheck.tld',function(err,addresses){\n  if(Array.isArray(addresses)) addresses.forEach(function(ip){\n    if(ignore[ip]) return;\n    console.log('ignoring a catch-all IP',ip);\n    ignore[ip] = true;\n  });\n});"
  },
  {
    "path": "lib/index.js",
    "content": "// TODO, migrate code from ../bin/*.js to a library here"
  },
  {
    "path": "notary.md",
    "content": "# Bitcoin Notary - Anonymous, Distributed, & Simple\n\n> work in progress\n\nThe 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.\n\nTerminology:\n\n* Notary - a sequential source of assertions / revocations of any txid (Stamps)\n* Stamp - a single notarization published in any transaction as an OP_RETURN\n\nA 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.\n\nAny 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.\n\nA Stamp is a 40-byte binary OP_RETURN containing:\n\n* 4 byte Notary ID\n* 16 byte token\n* 16 byte ciphertext\n* 4 byte MAC\n\nThe 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.\n\nThe MAC is the SipHash digest output of the Notary ID, key, and the full bitcoin transaction id (52 bytes) using the same cipher key.\n\nPositive values output to the OP_RETURN are an `assertion`, 0 values are a `revocation`.\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"blockname\",\n  \"version\": \"0.0.1\",\n  \"description\": \"A simple bitcoin-based DNS cache\",\n  \"main\": \"./lib/index.js\",\n  \"scripts\": {\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/quartzjer/blockname.git\"\n  },\n  \"keywords\": [\n    \"bitcoin\",\n    \"blockchain\",\n    \"dns\",\n    \"decentralized\",\n    \"crypto\",\n    \"cryptocurrency\"\n  ],\n  \"author\": \"Jeremie Miller\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/quartzjer/blockname/issues\"\n  },\n  \"homepage\": \"https://github.com/quartzjer/blockname\",\n  \"devDependencies\": {\n  },\n  \"dependencies\": {\n    \"bitcoinjs-lib\": \"^1.2.0\",\n    \"helloblock-js\": \"^0.2.5\",\n    \"ip\": \"^0.3.2\",\n    \"level-party\": \"^1.0.1\",\n    \"native-dns\": \"^0.7.0\",\n    \"raw-op-return\": \"git+https://github.com/quartzjer/raw-op-return.git\",\n    \"yargs\": \"^1.3.3\"\n  }\n}\n"
  }
]