├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── package.json └── test.js /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - node 5 | - lts/* 6 | - '10' 7 | 8 | jobs: 9 | include: 10 | - stage: npm release 11 | node_js: node 12 | script: echo "Deploying to npm ..." 13 | deploy: 14 | provider: npm 15 | email: 16 | secure: hTPq0tl08Csq/hyTWNfzdO0JwSFluhm9HzYKuqpQfipRQV7K57Lbqb5yvJAB11gToJcItMwPjzs0zn5ySskGKiExJ9G3P9h8SDLBsqUIp/QqxUa9nzP5hQG8eHOP+qHVzq0TvRQDdtPeg1HgEzq7qHQHWMbBZ5wTjZPSHeQJKI9HfcopHd/6vDfoQ2hFHOnmnyAVXVzXBZpRR2aYebcr4SQEqQGtTavlbNo6u1A32XPv80yjZyGZWm9lMtySyEEqaepdP/IhAIr2+/S/uZi8MDwDMKMzxXMcdejfIgx26dJhMG9QMEcxViMIxpfMFDQuXSSMxO218up2m2/7axjESVkgk/d/wf8t8rcUnNVSIZ5g3QySKXZs+23BAkSCp/L9mO9vu4xEdU/UG3uBbpqV4Q9XOyp9ux7wpCJkgyoJE/RNnDRS2e0C2wA7LMHJ1qYK+4jQ4pNZkbpXDZT+NLX4OI77KBRbqoNRFK5id/nJBvGoSNQAPNah7h64GUl9NFr7D1DJnewAY2bCABedSA0TKp+2wiE3ffXrRQyOxKhymh8+fRNA4xkIabf16LLyC2Rm+8p1WITczlEmzJfWQ3WUIyyrBI+Xd6JtMeBuEy+2J5xPzxYxMLc6sFc96awETSxeZcv1oJ4LfD+F+2il5LhUr5mbiH1rEcZVUpYgfmnRgVw= 17 | api_key: 18 | secure: moLS2L5W9l0EKDh0NRyPTo6KRM1PTlXLrpsFvOWGPcrZ/RVpDzZW14dE4Ta46/xCV8Tqd4sik9KVLHe3mAjzoyvg2uk+ObSpZC0sM4bw/zjIGrEVhZlsQmJ7xfEh+1c223Hf3+y+N9M58W7kHJAPdxboDNcGQ95fJSLerv3J20DJEVD2eeR17ReAEGavSaup6+nUccptio1ahdNWt/e9pnSMHJKmIOx7s0TPMpDl2+c6K+Yhk7tUkYhnHh9wCoLLNPUk+ESqInEnKpAQczppfoC1z676k6XohEg0BAFsubrSRRPQGEs6lwZAkXAayHs/OLGAoR+7UHga8GdmjBFPau4VV6Cxl8eJwb8+qjEiKSEAvFLHFuNY2XL6rQgzYv674TcKotkUjhEi4XIEzZxjzletmoL10MBj1rKpoGT7oiovvF2C6tgKVRu+d7kx7DE+1u/+pWGr855vtk55M1Q4f/5s+zvcGI6KGHt/ThmdNFRJbHHRv9tVWphtw0TXJPtBgFXvHjhrzcYZbV3HKtN0nyMdvGcdA17fGasHJJVKGgsbLUeS72ww1EuWAUn3QzvZgRngIjLGqJfkXtxToOMPvl4WeGttODBq7hcmmcviTK4FlV8Z0vr3Fmi9Ou9urfRgvwS/fqJGqKKXsmbwd8ZDbcJPv05bvZnq7A00y2ybplA= 19 | on: 20 | tags: true 21 | node: node 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, Emil Bay 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `rendezvous-point` 2 | 3 | [![Build Status](https://travis-ci.org/emilbayes/rendezvous-point.svg?branch=master)](https://travis-ci.org/emilbayes/rendezvous-point) 4 | 5 | > Rendezvous points on the DHT where two authenticated peers can leave data 6 | 7 | Data could be eg. a `ip:port` pair 8 | 9 | ## Usage 10 | 11 | ```js 12 | var rendezvous = require('rendezvous-point') 13 | 14 | var keypairA = {publicKey: Buffer.from('...'), secretKey: Buffer.from('...')} 15 | var peerA = rendezvous(dht, keypairA) 16 | 17 | var keypairB = {publicKey: Buffer.from('...'), secretKey: Buffer.from('...')} 18 | var peerB = rendezvous(dht, keypairB) 19 | 20 | // Leave a message for peerB on the DHT 21 | peerA.write(keypairB.publicKey, Buffer.from('Hello B!'), function (err) { 22 | if (err) throw err 23 | 24 | peerB.read(keypairA.publicKey, function (err, message) { 25 | if (err) throw err 26 | 27 | console.log(message.equal(Buffer.from('Hello B!'))) 28 | }) 29 | }) 30 | ``` 31 | 32 | ## API 33 | 34 | ### `var peer = rendezvous(dht, keypair)` 35 | 36 | Create a rendezvous instance for `bittorrent-dht` instance and a given 37 | `libsodium` `X25519` key pair, eg. generated with `crypto_kx_keypair` 38 | 39 | ### `peer.write(recepientPublicKey, buf, cb)` 40 | 41 | Write a message `buf` at the rendezvous point between `keypair` and 42 | `recepientPublicKey`, calling `cb(err, hash)` with any error or the resulting 43 | hash key in the DHT. 44 | 45 | ### `peer.read(senderPublicKey, cb)` 46 | 47 | Read any message at the rendezvous point between `keypair` and 48 | `senderPublicKey`, calling `cb(err, messageObj)` with any error or the message 49 | left in the DHT with the [DHT result object](https://github.com/webtorrent/bittorrent-dht#dhtgethash-opts-callback) 50 | 51 | 52 | ## Design 53 | 54 | The rendezvous point is a public key generated from a seed, where the seed is 55 | the shared secret from a Diffie-Hellman key exchange between two peers. This 56 | means that either side can leave messages in the DHT at the rendezvous point, 57 | because both can generate the same key pair. The rendezvous point can therefore 58 | be seen as a shared mailbox between the two peers. A future extension may be to 59 | leave messages encrypted to the other party, so messages are only readable by 60 | the other party and no one else who happens upon the rendezvous point (eg. an 61 | adverse DHT node). 62 | 63 | ## Install 64 | 65 | ```sh 66 | npm install rendezvous-point 67 | ``` 68 | 69 | ## License 70 | 71 | [ISC](LICENSE) 72 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var sodium = require('sodium-native') 2 | var crypto = require('crypto') 3 | var assert = require('nanoassert') 4 | 5 | module.exports = Rendezvous 6 | function Rendezvous (dht, keypair) { 7 | if (!(this instanceof Rendezvous)) return new Rendezvous(dht, keypair) 8 | assert(keypair) 9 | assert(keypair.publicKey.byteLength === sodium.crypto_kx_PUBLICKEYBYTES) 10 | assert(keypair.secretKey.byteLength === sodium.crypto_kx_SECRETKEYBYTES) 11 | 12 | this.dht = dht 13 | this.keypair = keypair 14 | } 15 | 16 | Rendezvous.prototype.write = function (remoteKey, message, cb) { 17 | assert(remoteKey.byteLength === sodium.crypto_kx_PUBLICKEYBYTES) 18 | assert(message.byteLength <= 1000) 19 | 20 | var self = this 21 | var rendevousPoint = Buffer.allocUnsafe(sodium.crypto_kx_SESSIONKEYBYTES) 22 | 23 | sodium.crypto_kx_server_session_keys( 24 | rendevousPoint, 25 | null, 26 | self.keypair.publicKey, 27 | self.keypair.secretKey, 28 | remoteKey 29 | ) 30 | 31 | var discoveryPk = Buffer.alloc(sodium.crypto_sign_PUBLICKEYBYTES) 32 | var discoverySk = sodium.sodium_malloc(sodium.crypto_sign_SECRETKEYBYTES) 33 | sodium.crypto_sign_seed_keypair(discoveryPk, discoverySk, rendevousPoint) 34 | 35 | self.dht.put({ 36 | v: message, 37 | k: discoveryPk, 38 | seq: 0, 39 | sign: function (buf) { 40 | var sig = Buffer.alloc(sodium.crypto_sign_BYTES) 41 | sodium.crypto_sign_detached(sig, buf, discoverySk) 42 | return sig 43 | } 44 | }, cb) 45 | } 46 | 47 | Rendezvous.prototype.read = function (remoteKey, cb) { 48 | assert(remoteKey.byteLength === sodium.crypto_kx_PUBLICKEYBYTES) 49 | 50 | var self = this 51 | var rendevousPoint = Buffer.allocUnsafe(sodium.crypto_kx_SESSIONKEYBYTES) 52 | 53 | sodium.crypto_kx_client_session_keys( 54 | null, 55 | rendevousPoint, 56 | self.keypair.publicKey, 57 | self.keypair.secretKey, 58 | remoteKey 59 | ) 60 | 61 | var discoveryPk = Buffer.alloc(sodium.crypto_sign_PUBLICKEYBYTES) 62 | var discoverySk = sodium.sodium_malloc(sodium.crypto_sign_SECRETKEYBYTES) 63 | sodium.crypto_sign_seed_keypair(discoveryPk, discoverySk, rendevousPoint) 64 | var hash = crypto.createHash('sha1').update(discoveryPk).digest() 65 | 66 | self.dht.get(hash, { 67 | verify: sodium.crypto_sign_verify_detached, 68 | cache: false 69 | }, cb) 70 | } 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rendezvous-point", 3 | "version": "2.0.0", 4 | "description": "Rendezvous points on the DHT where two authenticated peers can leave data", 5 | "main": "index.js", 6 | "dependencies": { 7 | "nanoassert": "^1.1.0", 8 | "sodium-native": "^2.2.1" 9 | }, 10 | "devDependencies": { 11 | "bittorrent-dht": "^8.4.0" 12 | }, 13 | "scripts": { 14 | "test": "node test.js" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/emilbayes/rendezvous-point.git" 19 | }, 20 | "keywords": [ 21 | "dht", 22 | "bittorrent", 23 | "mutable", 24 | "bep 44" 25 | ], 26 | "author": "Emil Bay ", 27 | "license": "ISC", 28 | "bugs": { 29 | "url": "https://github.com/emilbayes/rendezvous-point/issues" 30 | }, 31 | "homepage": "https://github.com/emilbayes/rendezvous-point#readme" 32 | } 33 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var Rendezvous = require('.') 2 | var sodium = require('sodium-native') 3 | 4 | var DHT = require('bittorrent-dht') 5 | var assert = require('nanoassert') 6 | 7 | var dht = new DHT() 8 | 9 | dht.listen(20000, function loop () { 10 | var keyA = keygen() 11 | var keyB = keygen() 12 | 13 | var peerA = new Rendezvous(dht, keyA) 14 | var peerB = new Rendezvous(dht, keyB) 15 | 16 | peerA.write(keyB.publicKey, Buffer.from('Hello world!'), function (err, hash) { 17 | if (err) throw err 18 | 19 | peerB.read(keyA.publicKey, function (err, obj) { 20 | if (err) throw err 21 | 22 | assert(obj.v.equals(Buffer.from('Hello world!'))) 23 | 24 | dht.destroy() 25 | }) 26 | }) 27 | }) 28 | 29 | function keygen () { 30 | var pk = sodium.sodium_malloc(sodium.crypto_kx_PUBLICKEYBYTES) 31 | var sk = sodium.sodium_malloc(sodium.crypto_kx_SECRETKEYBYTES) 32 | 33 | sodium.crypto_kx_keypair(pk, sk) 34 | 35 | return {publicKey: pk, secretKey: sk} 36 | } 37 | --------------------------------------------------------------------------------