GithubHelp home page GithubHelp logo

hyperdht's Introduction

hyperdht

The DHT powering Hyperswarm

npm install hyperdht

Built on top of dht-rpc.

The Hyperswarm DHT uses a series of holepunching techniques to make sure connectivity works on most networks, and is mainly used to facilitate finding and connecting to peers using end to end encrypted Noise streams.

Usage

To try it out, first instantiate a DHT instance

import DHT from 'hyperdht'

const node = new DHT()

Then on one computer listen for connections

// create a server to listen for secure connections
const server = node.createServer()

server.on('connection', function (socket) {
  // socket is E2E encrypted between you and the other peer
  console.log('Remote public key', socket.remotePublicKey)

  // pipe it somewhere like any duplex stream
  process.stdin.pipe(socket).pipe(process.stdout)
})

// make a ed25519 keypair to listen on
const keyPair = DHT.keyPair()

// this makes the server accept connections on this keypair
await server.listen(keyPair)

Then on another connect to the computer using the public key of the key-pair it is listening on

// publicKey here is keyPair.publicKey from above
const socket = anotherNode.connect(publicKey)

socket.on('open', function () {
  // socket fully open with the other peer
})

// pipe it somewhere like any duplex stream
process.stdin.pipe(socket).pipe(process.stdout)

API

const node = new DHT([options])

Create a new DHT node.

Options include:

{
  // Optionally overwrite the default bootstrap servers, just need to be an array of any known dht node(s)
  // Defaults to ['node1.hyperdht.org:49737', 'node2.hyperdht.org:49737', 'node3.hyperdht.org:49737']
  bootstrap: ['host:port'],
  keyPair, // set the default key pair to use for server.listen and connect
  connectionKeepAlive // set a default keep-alive (in ms) on all opened sockets. Defaults to 5000. Set false to turn off (advanced usage).
}

See dht-rpc for more options as HyperDHT inherits from that.

Note: The default bootstrap servers are publicly served on behalf of the commons. To run a fully isolated DHT, start one or more dht nodes with an empty bootstrap array (new DHT({bootstrap:[]})) and then use the addresses of those nodes as the bootstrap option in all other dht nodes. You'll need at least one persistent node for the network to be completely operational.

keyPair = DHT.keyPair([seed])

Use this method to generate the required keypair for DHT operations.

Returns an object with {publicKey, secretKey}. publicKey holds a public key buffer, secretKey holds a private key buffer.

If you pass any options they are forwarded to dht-rpc.

await node.destroy([options])

Fully destroy this DHT node.

This will also unannounce any running servers. If you want to force close the node without waiting for the servers to unannounce pass { force: true }.

node = DHT.bootstrapper(port, host, [options])

If you want to run your own Hyperswarm network use this method to easily create a bootstrap node.

Creating P2P servers

const server = node.createServer([options], [onconnection])

Create a new server for accepting incoming encrypted P2P connections.

Options include:

{
  firewall (remotePublicKey, remoteHandshakePayload) {
    // validate if you want a connection from remotePublicKey
    // if you do return false, else return true
    // remoteHandshakePayload contains their ip and some more info
    return true
  }
}

You can run servers on normal home computers, as the DHT will UDP holepunch connections for you.

await server.listen(keyPair)

Make the server listen on a keyPair. To connect to this server use keyPair.publicKey as the connect address.

server.refresh()

Refresh the server, causing it to reannounce its address. This is automatically called on network changes.

server.on('connection', socket)

Emitted when a new encrypted connection has passed the firewall check.

socket is a NoiseSecretStream instance.

You can check who you are connected to using socket.remotePublicKey and socket.handshakeHash contains a unique hash representing this crypto session (same on both sides).

server.on('listening')

Emitted when the server is fully listening on a keyPair.

server.address()

Returns an object containing the address of the server:

{
  host, // external IP of the server,
  port, // external port of the server if predictable,
  publicKey // public key of the server
}

You can also get this info from node.remoteAddress() minus the public key.

await server.close()

Stop listening.

server.on('close')

Emitted when the server is fully closed.

Connecting to P2P servers

const socket = node.connect(remotePublicKey, [options])

Connect to a remote server. Similar to createServer this performs UDP holepunching for P2P connectivity.

The remote public key can be encoded as either a buffer, a hex string or a z-base32 string.

Options include:

{
  nodes: [...], // optional array of close dht nodes to speed up connecting
  keyPair // optional key pair to use when connection (defaults to node.defaultKeyPair)
}

socket.on('open')

Emitted when the encrypted connection has been fully established with the server.

socket.remotePublicKey

The public key of the remote peer.

socket.publicKey

The public key of the local socket.

Additional peer discovery

const stream = node.lookup(topic, [options])

Look for peers in the DHT on the given topic. Topic should be a 32 byte buffer (normally a hash of something).

The returned stream looks like this

{
  // Who sent the response?
  from: { id, host, port },
  // What address they responded to (i.e. your address)
  to: { host, port },
  // List of peers announcing under this topic
  peers: [ { publicKey, nodes: [{ host, port }, ...] } ]
}

To connect to the peers you should afterwards call connect with those public keys.

If you pass any options they are forwarded to dht-rpc.

const stream = node.announce(topic, keyPair, [relayAddresses], [options])

Announce that you are listening on a key-pair to the DHT under a specific topic.

When announcing you'll send a signed proof to peers that you own the key-pair and wish to announce under the specific topic. Optionally you can provide up to 3 nodes, indicating which DHT nodes can relay messages to you - this speeds up connects later on for other users.

An announce does a parallel lookup so the stream returned looks like the lookup stream.

Creating a server using dht.createServer automatically announces itself periodically on the key-pair it is listening on. When announcing the server under a specific topic, you can access the nodes it is close to using server.nodes.

If you pass any options they are forwarded to dht-rpc.

await node.unannounce(topic, keyPair, [options])

Unannounce a key-pair.

If you pass any options they are forwarded to dht-rpc.

Mutable/immutable records

const { hash, closestNodes } = await node.immutablePut(value, [options])

Store an immutable value in the DHT. When successful, the hash of the value is returned.

If you pass any options they are forwarded to dht-rpc.

const { value, from } = await node.immutableGet(hash, [options])

Fetch an immutable value from the DHT. When successful, it returns the value corresponding to the hash.

If you pass any options they are forwarded to dht-rpc.

const { publicKey, closestNodes, seq, signature } = await node.mutablePut(keyPair, value, [options])

Store a mutable value in the DHT.

If you pass any options they are forwarded to dht-rpc.

const { value, from, seq, signature } = await node.mutableGet(publicKey, [options])

Fetch a mutable value from the DHT.

Options:

  • seq - OPTIONAL, default 0, a number which will only return values with corresponding seq values that are greater than or equal to the supplied seq option.
  • latest - OPTIONAL - default false, a boolean indicating whether the query should try to find the highest seq before returning, or just the first verified value larger than options.seq it sees.

Any additional options you pass are forwarded to dht-rpc.

Additional API

See dht-rpc for the additional APIs the DHT exposes.

CLI

You can start a DHT node in the command line:

npm install -g hyperdht

Run a DHT node:

hyperdht # [--port 0] [--host 0.0.0.0] [--bootstrap <comma separated list of ip:port>]

Or run multiple nodes:

hyperdht --nodes 5 # [--host 0.0.0.0] [--bootstrap <list>]

Note: by default it uses the mainnet bootstrap nodes.

Isolated DHT network

To create your own DHT network is as follows:

  1. Run your first bootstrap node:
hyperdht --bootstrap --host (server-ip) # [--port 49737]

Important: it requires the port to be open.

Now your bootstrap node is ready to use at (server-ip):49737, for example:

const dht = new DHT({ bootstrap: ['(server-ip):49737'] })

Note: You could configure some DNS for the bootstrap IP addresses.

For the network to be fully operational it needs at least one persistent node.

  1. Provide the first node by using your own bootstrap values:
hyperdht --port 49738 --bootstrap (server-ip):49737

Important: it requires the port to be open too.

You need to wait ~30 mins for the node to become persistent.

Having persistent nodes in different places makes the network more decentralized and resilient!

For more information: examples/isolated-dht.mjs

License

MIT

hyperdht's People

Contributors

4c656554 avatar anyass3 avatar aral avatar bendik avatar billiegoose avatar davidmarkclements avatar dmonad avatar freeall avatar hdegroote avatar kasperisager avatar lukks avatar mafintosh avatar martinheidegger avatar oodaspace avatar pfrazee avatar rafapaezbas avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hyperdht's Issues

Secure and privacy preserving DHT

Good job with Hyperswarm! 👏 👏

I reckon there are no mechanisms in place in the DHT for protecting peers against passive and active attacks that could a) easily reveal the intentions of lookup initiators by leaking DHT requests and routing requests and b) allow active attackers to perform many different routing attacks, effectively serving poisonous content to lookup initiators.

Is the Hyperswam DHT somehow taking these potential vulnerabilities into consideration? If not, are there any plans to address these issues at any point? I'd be glad to discuss and help, if the topic is relevant for Hyperswarm.

Cannot read property length of null when using flushed() in Hyperswarm

The following error started occurring when using flushed() in server mode in Hyperswarm. This only started happening in v5.0.13 and does not appear when reverting to v5.0.12.

TypeError: Cannot read property 'length' of null
    at Object.preencode (.../node_modules/compact-encoding/index.js:309:34)
    at Object.preencode (.../node_modules/@hyperswarm/dht/lib/messages.js:179:15)
    at Object.encode (.../node_modules/compact-encoding/index.js:371:7)
    at annSignable (.../node_modules/@hyperswarm/dht/lib/persistent.js:261:7)
    at signAnnounce (.../node_modules/@hyperswarm/dht/lib/persistent.js:237:44)
    at HyperDHT._requestAnnounce (.../node_modules/@hyperswarm/dht/index.js:306:27)
    at commit (.../node_modules/@hyperswarm/dht/index.js:138:18)
    at Query.commit [as _commit] (.../node_modules/@hyperswarm/dht/index.js:94:14)

Add userData to dht.announce()

Proposal

I suggest adding userData field to peer encoding.

{
      publicKey: c.fixed32.decode(state),
      relayAddresses: ipv4Array.decode(state),
      userData: c.buffer.decode(state),
}

Reasoning

My understanding is that upon making dht.lookup request, applications can expect up to 400 random peers.

With no further information, apps are left with the only option of connecting to all these peers before making any judgment on whether they are honest, authorized, or spam, or which peers are most likely to have what they need.

Each of these connections requires the DHT to facilitate the hole-punching, if the client isn't already connected or blocked these peers.

Adding userData would allow applications to make this app-specific judgment, helping them minimize their load on the DHT.

Use cases

Some use cases from the top of my head:

  1. Spam protection, as clients prioritize servers with proper attestations from the source author, either direct or delegated.
  2. Announcing alternative transports servers may support. That is helpful for:
    • Allow skipping hole-punching for servers with publicly accessible addresses.
    • Browsers can use DHT relays only for lookup, then connect through advertised HTTP/Websocket servers.
  3. L2 overlay networks, that use the DHT for what it is good for, but then offer more features like persistent records, and experiment with defensive measures to fight abuse, including computation cost, or fees etc. where the userData relays all the required proofs. All without needing any involvement from the DHT.

Why not mutablePut

For all the reasons why we need swarm.join in the first place: capabilities vs ACL.
This proposal would enable async capabilities for applications that can afford static proofs, as opposed to requiring a handshake.

Also, mutablePut can only accept Ed25519 signatures, while in userData you can attach attestations signed according to any scheme the application layer supports.

examples

  • Seeders of Hypercores, pointing to alternative supported transports + showing a delegation proof from the author of the core, in this example the core is using secp256k1 instead of ed25519 in the auth function, which is not possible with something like https://github.com/holepunchto/hyperswarm-seeders:
{
   "alternative_endpoints": {
      "WebSocket": ["wss://foo.example.com", "wss://example.org:8080"],
      "WebTransport": ["https://example.com:4999/wt"],
      "QUIC": ["https://example.com:5000"],
   },
   "author_attestation": {
      "attestation": {
          "seeder": <key>,
          "ttl": <timestamp>,
          "priority": 1,
       },
      "keyType": "secp256k1",
      "signature": <signature>,
   },
}

Risk

I assume the only risk here is the same as in mutablePut, namely spam. Demanding a max size for userData should help here, but it this proposal doesn't assume how should the DHT fight the abuse of both. Only that it doesn't introduce a new attack vector that doesn't already exist with mutablePut.
Apparently there is an implicit max size anyways, so doesn't seem anymore abusable than mutablePut https://gist.github.com/Nazeh/ce4475b1a7c668db064beb326f915eed

I don't think this would be a breaking change either. Trying to keep m.peer somewhat backward compatible is proving to be too complicated, this will probably need to be a major version.

Running private DHTs

README says:

To run a fully private DHT, start two or more dht nodes with an empty bootstrap array (dht({bootstrap:[]})) and then use the addresses of those nodes as the bootrstrap option in all other dht nodes.

But dht({ bootstrap: [] }) never fires a listening event.

Can you show an example for running a private bootstrap node?

Sending HTTP traffic over Hyperswarm. Should we consider supporting multiplexing in the protocol?

Sending HTTP traffic over Hyperswarm sockets is a use-case I'm currently exploring. This leads to a small challenge:

HTTP semantics depend on the connection life-cycle. After a response is finished, the server is expected to close the connection. This seems like a problem for Hyperswarm, because connection setup isn't as quick as it is for traditional TCP. We don't want to open and close connections for every request.

One option is to use empty messages to indicate the end of a request or response, without closing the stream. This should work so long as responses are sent in the same order that requests are sent, but that's a performance penalty.

We can improve on that by using multiplexing such as in libp2p-mplex. This would allow multiple in-flight HTTP request/responses, and the "channels" could be freed for reuse after the req/res sessions end on each of them.

I'm opening this issue for two reasons:

  1. To confirm that I understand the situation correctly. (not a given)
  2. If I'm correct, I wonder if there'd be any merit to supporting multiplexing natively in the protocol? I have two reasons for suggesting that: A) It might be useful in other contexts, and B) I suspect that sending HTTP over Hyperswarm sockets will be really common, and since Hyperswarm sockets don't have any "standard header" to declare the message format they're about to send, having multiplexing on all sockets would simplify things.

server (socket-pairer) throws if client write() without noise

Server throws:

buffer.js:560
      if (list[i].length) {
                  ^

TypeError: Cannot read property 'length' of null
    at Function.concat (buffer.js:560:19)
    at Socket.onreadable (/self/hyperforward/node_modules/@hyperswarm/dht/lib/socket-pairer.js:606:36)
    at Socket.emit (events.js:400:28)
    at emitReadable_ (internal/streams/readable.js:555:12)
    at onEofChunk (internal/streams/readable.js:533:5)
    at readableAddChunk (internal/streams/readable.js:247:5)
    at Socket.Readable.push (internal/streams/readable.js:206:10)
    at TCP.onStreamRead (internal/stream_base_commons.js:238:12)

client.js

const DHT = require('@hyperswarm/dht')
const net = require('net')

const serverKeyPair = DHT.keyPair(Buffer.from('524ad00b147e1709e7fd99e2820f8258fd30ed043c631233ac35e17f9ec10333', 'hex'))
const clientKeyPair = DHT.keyPair(Buffer.from('c7f7b6cc2cd1869a4b8628deb49efc992109c9fbdfa55ab1cfa528117fff9acd', 'hex'))

const node = new DHT({ keyPair: clientKeyPair })

const peer = node.connect(serverKeyPair.publicKey)
peer.on('open', function () {
  console.log('peer', peer.rawStream.remoteAddress + ':' + peer.rawStream.remotePort)

  const socket = net.connect(peer.rawStream.remotePort, peer.rawStream.remoteAddress)
  socket.end('hey')
})

server.js

const DHT = require('@hyperswarm/dht')
const net = require('net')

const serverKeyPair = DHT.keyPair(Buffer.from('524ad00b147e1709e7fd99e2820f8258fd30ed043c631233ac35e17f9ec10333', 'hex'))
const clientKeyPair = DHT.keyPair(Buffer.from('c7f7b6cc2cd1869a4b8628deb49efc992109c9fbdfa55ab1cfa528117fff9acd', 'hex'))

const node = new DHT({ keyPair: serverKeyPair })

const server = node.createServer({
  firewall: function (remotePublicKey, remoteHandshakePayload) {
    console.log('on firewall, public key:\n' + remotePublicKey.toString('hex'))
    return !remotePublicKey.equals(clientKeyPair.publicKey)
  }
})

server.on('connection', function (peer) {
  console.log('peer', peer.rawStream.remoteAddress + ':' + peer.rawStream.remotePort)
})

server.listen(serverKeyPair).then(() => console.log('server listening'))

It only throws if the client can reach server's ip:port, for example, listen and connect in the same local network or my machine as client -> DO as server (DO has ports open).

Aside from the issue, I just started experimenting with P2P so obviously I'm missing a lot but DHT should be able to connect(port, address) at will, while there is an active peer connection that keeps alive the initial holepunch.

In case two peers already knows each other ip:port (like two friends wanting to connect), it would be great to have a holepunch(port[, address]) and connect(port[, address]) to avoid querying bootstrap nodes and making direct connections.

Also, it's not crazy to think about having optional proxy support for bootstrap queries and connect() (which can fallback to relay servers and in that case it's ok), if an user trusts his proxy (like ProtonVPN or Tor) then it would extremely increase the DHT privacy and it can attract a lot of new users.

ProtonVPN has some P2P proxies, I never tested them but maybe in that specific case holepunch works and won't fallback to relay servers. I tested once a Plus proxy from Proton (which is paid) but the connection doesn't happen, that's why I said it would fallback to relays.

Unable to holepunch from the local network to a Docker container

Hey!

I am not able to connect to a DHT server running in a Docker container, from the same local network. To be clear, the Docker stack has its own Docker network, which lives in my home network. Here is a summary of the situation (I used @hyperswarm/doctor for the tests):

telegram-cloud-photo-size-4-5805510472735242539-y

Apparently, it may be because as both the clients and the server have the same external IP X.X.X.X, it tries to connect via LAN, but in reality the server is in a container with its own network.

Here is the full error, client-side
Waiting for node to be fully bootstrapped to collect info...

Node info:
- remote host: X.X.X.X
- remote port: 56888
- firewalled: true
- nat type: consistent

Connecting to test server...
Sending 32 bytes for the server to echo
/home/louneskmt/test/node_modules/.pnpm/@[email protected]/node_modules/@hyperswarm/dht/lib/connect.js:154
  const onabort = () => c.encryptedSocket.destroy(HOLEPUNCH_ABORTED())
                                                  ^

DHTError: HOLEPUNCH_ABORTED: Holepunch aborted
    at Holepuncher.onabort (/home/louneskmt/test/node_modules/.pnpm/@[email protected]/node_modules/@hyperswarm/dht/lib/connect.js:154:51)
    at Holepuncher.destroy (/home/louneskmt/test/node_modules/.pnpm/@[email protected]/node_modules/@hyperswarm/dht/lib/holepuncher.js:279:31)
    at Holepuncher._autoDestroy (/home/louneskmt/test/node_modules/.pnpm/@[email protected]/node_modules/@hyperswarm/dht/lib/holepuncher.js:267:31)
    at Holepuncher._consistentProbe (/home/louneskmt/test/node_modules/.pnpm/@[email protected]/node_modules/@hyperswarm/dht/lib/holepuncher.js:218:10)
Emitted 'error' event on NoiseSecretStream instance at:
    at WritableState.afterDestroy (/home/louneskmt/test/node_modules/.pnpm/[email protected]/node_modules/streamx/index.js:444:19)
    at NoiseSecretStream._destroy (/home/louneskmt/test/node_modules/.pnpm/@[email protected]/node_modules/@hyperswarm/secret-stream/index.js:463:5)
    at WritableState.updateNonPrimary (/home/louneskmt/test/node_modules/.pnpm/[email protected]/node_modules/streamx/index.js:189:16)
    at WritableState.update (/home/louneskmt/test/node_modules/.pnpm/[email protected]/node_modules/streamx/index.js:174:70)
    at WritableState.afterOpen (/home/louneskmt/test/node_modules/.pnpm/[email protected]/node_modules/streamx/index.js:501:27)
    at NoiseSecretStream._predestroy (/home/louneskmt/test/node_modules/.pnpm/@[email protected]/node_modules/@hyperswarm/secret-stream/index.js:390:7)
    at NoiseSecretStream.destroy (/home/louneskmt/test/node_modules/.pnpm/[email protected]/node_modules/streamx/index.js:572:12)
    at Holepuncher.onabort (/home/louneskmt/test/node_modules/.pnpm/@[email protected]/node_modules/@hyperswarm/dht/lib/connect.js:154:43)
    at Holepuncher.destroy (/home/louneskmt/test/node_modules/.pnpm/@[email protected]/node_modules/@hyperswarm/dht/lib/holepuncher.js:279:31)
    at Holepuncher._autoDestroy (/home/louneskmt/test/node_modules/.pnpm/@[email protected]/node_modules/@hyperswarm/dht/lib/holepuncher.js:267:31) {
  code: 'HOLEPUNCH_ABORTED'
}

Node.js v18.12.1

I have been able to make it work by manually setting remoteHolepunchable to false here:
https://github.com/hyperswarm/dht/blob/e2b4cb8588d1954765f176c2f092ecf72966f2d3/lib/connect.js#L139

Received new connection from 172.81.0.1
Connection from 172.81.0.1 was closed 

Thanks!

implement get and put

similar api to bitorrent-dht get/put

needs four dht-rpc commands

  • mutable-get
  • mutable-put
  • immutable-get
  • immutable-put

allow offline signing

accept opt.keypair.publicKey + opt.sig as an alternative to opts.keypair.publicKey + opts.keypair.secretKey

Strange error.

att @mafintosh suggested I post here

This is occurring when I leave nodes running for a long time...

image

I have no idea how to collect further information on this issue, but it has happened twice.

Expand "DHT" acronym in project byline

Currently, clicking on this project ("dht") shows a byline:

"The DHT powering the HyperSwarm stack"

It would be wonderful to expand that into Distributed Hash Table, which is what I think it stands for (and not Dihydrotestosterone--kidding! :)

Output Public key to file and read into another file

I am trying to output the Public key from the server into another file which would be the client

// make a ed25519 keypair to listen on
const keyPair = DHT.keyPair(Buffer.alloc(32).fill('testing-channel'))
// print the public key bytes as a base64 encoded string
console.log(keyPair['publicKey'].toString('base64'));
// output:
// 7a6HaUKi4sZ2EsP3J3kqHvkhkJETYbLjxkx3gTGbqS8=

I tried to store this base64 string as a variable in the client using the same library and still can't connect to the server!
Here is the client code

import DHT from '@hyperswarm/dht'
const anotherNode = new DHT()

// publicKey here is keyPair.publicKey from above
var key = Buffer.from('7a6HaUKi4sZ2EsP3J3kqHvkhkJETYbLjxkx3gTGbqS8=', 'base64').toString()

const noiseSocket = anotherNode.connect(key)

noiseSocket.on('open', function () {
  // noiseSocket fully open with the other peer
})

// pipe it somewhere like any duplex stream
process.stdin.pipe(noiseSocket).pipe(process.stdout)

The corresponding error:

  sodium.crypto_generichash(out, data)
         ^

TypeError: in must be an instance of TypedArray
    at hash (C:\Users\User\Documents\project\node_modules\@hyperswarm\dht\lib\crypto.js:6:10)
    at connect (C:\Users\User\Documents\project\node_modules\@hyperswarm\dht\lib\connect.js:19:13)
    at HyperDHT.connect (C:\Users\User\Documents\project\node_modules\@hyperswarm\dht\index.js:48:12)
    at file:///C:/Users/User/Documents/project/client.js:10:33
    at ModuleJob.run (node:internal/modules/esm/module_job:195:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:337:24)
    at async loadESM (node:internal/process/esm_loader:88:5)
    at async handleMainPromise (node:internal/modules/run_main:61:12)

Can anybody tell me how I can store the public key bytes as a variable through like base64 or JSON.stringify() or as a hex string?

There appears to be a line order error in index.js

There was an uncaught error ReferenceError: Cannot access 'error' before initialization
at Timeout.ontimeout [as _onTimeout] (C:\app\dev\test\node_modules@hyperswarm\dht\index.js:216:7)
at listOnTimeout (internal/timers.js:555:17)
at processTimers (internal/timers.js:498:7)

Same code works except in DigitalOcean servers

Running server.js and client.js on the same machine works.
Server.js on my machine and client.js on DigitalOcean also works, and vice versa (me as client and DO as server).

Except runinng both in separated DigitalOcean servers:
Server: https://gist.github.com/LuKks/7b56e221d9e3214bce56f562d0d65c60
Client: https://gist.github.com/LuKks/82c5816dbcafcee73cf7fcaecec449fc

events.js:377
      throw er; // Unhandled 'error' event
      ^

Error: Connect aborted
    at Pair.c.pair.ondestroy (/root/hyperforward/node_modules/@hyperswarm/dht/lib/connect.js:218:38)
    at Pair.destroy (/root/hyperforward/node_modules/@hyperswarm/dht/lib/socket-pairer.js:485:10)
    at Timeout.destroy (/root/hyperforward/node_modules/@hyperswarm/dht/lib/socket-pairer.js:687:5)
    at listOnTimeout (internal/timers.js:559:11)
    at processTimers (internal/timers.js:500:7)
Emitted 'error' event on NoiseSecretStream instance at:
    at WritableState.afterDestroy (/root/hyperforward/node_modules/streamx/index.js:442:19)
    at NoiseSecretStream._destroy (/root/hyperforward/node_modules/@hyperswarm/secret-stream/index.js:365:5)
    at WritableState.updateNonPrimary (/root/hyperforward/node_modules/streamx/index.js:189:16)
    at WritableState.update (/root/hyperforward/node_modules/streamx/index.js:174:70)
    at ReadableState.afterOpen (/root/hyperforward/node_modules/streamx/index.js:499:27)
    at NoiseSecretStream._predestroy (/root/hyperforward/node_modules/@hyperswarm/secret-stream/index.js:308:7)
    at NoiseSecretStream.destroy (/root/hyperforward/node_modules/streamx/index.js:570:12)
    at Pair.c.pair.ondestroy (/root/hyperforward/node_modules/@hyperswarm/dht/lib/connect.js:218:23)
    at Pair.destroy (/root/hyperforward/node_modules/@hyperswarm/dht/lib/socket-pairer.js:485:10)
    at Timeout.destroy (/root/hyperforward/node_modules/@hyperswarm/dht/lib/socket-pairer.js:687:5)

Just sharing, the more I try to understand the more I know it's a great lib!

Updating mutable data without updating seq

While playing aroud with mutable.put, I was trying to force an error for not updating seq while updating a value. I did expect this to raise an error, but instead the callback has been called with err undefined.

When calling mutable.get to get the value, I've received the value that was set with the initial put call. Is this intended behaviour?

This can easily be replicated by removing the sequence update from the mutable-put-get example: https://github.com/hyperswarm/dht/blob/master/examples/mutable-put-get.js#L26

remove node net module

After we get the new utp module done, we should remove net module to reduce size/complexity. It's only used in a few places.

%grep "require('net" -R . 
./node_modules/bind-easy/test.js:const net = require('net')
./node_modules/bind-easy/index.js:const net = require('net')
./node_modules/@hyperswarm/dht/test/connections.js:const net = require('net')
./node_modules/@hyperswarm/dht/lib/socket-pairer.js:const net = require('net')
./node_modules/@hyperswarm/secret-stream/test.js:const net = require('net')

Add ability to persist and load peers to/from JSON

As discussed on IRC, Russia is going to play around with closing it's network off from the rest of the internet.

This could kill Dat applications by blocking the DHT bootstrap nodes.

A good step would be to support persisting known nodes to help bootstrapping, and to account for scenarios where the bootstrap nodes aren't accessible.

Privacy Preserving in @hyperswarm/dht

@mafintosh

I opened a similar issue here: holepunchto/hyperswarm#81

In search for info about privacy preserving DHTs, I found these interesting papers:

https://www.gpestana.com/blog/in-pursuit-of-private-dhts/

"Some of the desirable properties of a private and metadata resistant DHT are:

    Anonymity for producers of content: tracking down who was the originator of content stored in the DHT should not be possible.

    Anonymity for consumers of content: nodes that request content from the DHT should not be linked to the requested content by external actors.

    Plausible deniability of the files hosted in the network nodes: when peers query for content in the DHT, they should not be able to identify which peers are storing the content.
"

https://raw.githubusercontent.com/gpestana/p2psec/master/papers/privacy_preserving_dht/privacy_preserving_dht.pdf

https://www.gpestana.com/papers/everyone-is-naked-rev.pdf

where privacy vulnerabilities within a DHT are described.

In the last paragraph of the second paper, few Privacy Enhancing Technologies (PETs) are outlined and suggested for DHTs.
Namely: Onion Routines, and cryptographic tools: Multi-Party Computation and Zero Knowledge primitives.

What are the mechanisms and the tools deployed in Hyperswarm in order to enhance and preserve, as much as possible, privacy?
Would it be feasible to implement the Onion Routines within or around Hyperswarm/dht?

I found this on-going javascript implementation of tor protocol: https://github.com/Ayms/node-Tor . Please, have a look at it. It would be awesome if Hyperswarm would incorporate and use it. Looking forward to your feedback

In this good overview of the Hyper Stack it's summarized everything needed about this theme:

https://github.com/tradle/why-hypercore/blob/master/FAQ.md#is-hyperswarm-anonymous

" Is Hyperswarm anonymous?

No. Let's explore what is revealed. Hyperswarm announces IP and Port of the peer to allow other peers in P2P network to connect with them. Hyperswarm's DHT holds that data, so any observer could simply collect this information. The observer will also learn the topic this peer is advertizing. Aside from that no other information is leaked. Is it worse than DNS? In DNS servers also announce their name and address to the world. But clients do not, while in Hyperswarm they do. On the other hand, topic name is more private than in DNS, it is just some hash, not a human-readable name.

So what can be done to protect IP addresses in DHT?

    Hyperswarm can be improved to encrypt data in DHT, and this way only the peers that know some shared secret could find each other.

    Potentially I2P can be used in the future.
"

So.. the questions are: how to effectively deploy these two improvements?

Add userData to handshake

Maf mentioned the potential to include userData in the handshake. I'd personally like to use that to declare the intended protocol so that I don't have to go sniffing packets.

Connection metadata

We should expose more metadata about connections, ie.

  • remoteIp
  • holepunchStrategy
  • isLocalNetwork
  • connectionType, utp, udx, tcp etc

etc, we might already expose some and if so document how to access it

multiple mutablePut failure (maybe my misunderstanding?)

I am new to this software, & really enjoying it. I can get a bootstrapped private DHT up and running, start servers & connect to them, mutablePut a value and then mutableGet that value back out. It's really cool!

However, when I try to mutablePut a new value onto the same key, my bootstrap nodes error out with this message:

/home/robin/dht-fun/node_modules/compact-encoding/index.js:280
      state.buffer.set(s, state.start)
                   ^

TypeError: Cannot convert undefined or null to object
    at Buffer.set (<anonymous>)
    at Object.encode (/home/robin/dht-fun/node_modules/compact-encoding/index.js:280:20)
    at Object.encode (/home/robin/dht-fun/node_modules/@hyperswarm/dht/lib/messages.js:269:15)
    at Object.encode (/home/robin/dht-fun/node_modules/compact-encoding/index.js:373:7)
    at Persistent.onmutableput (/home/robin/dht-fun/node_modules/@hyperswarm/dht/lib/persistent.js:186:26)
    at HyperDHT.onrequest (/home/robin/dht-fun/node_modules/@hyperswarm/dht/index.js:278:26)
    at HyperDHT._onrequest (/home/robin/dht-fun/node_modules/dht-rpc/index.js:399:14)
    at IO.onmessage (/home/robin/dht-fun/node_modules/dht-rpc/lib/io.js:53:12)
    at Socket.emit (node:events:390:28)
    at UDP.onMessage [as onmessage] (node:dgram:939:8)

That's triggered by this code from a client node, in which I attempt to mutablePut again onto a public key that's previously been mutablePut onto:

const mutableKeyPair = DHT.keyPair(Buffer.alloc(32).fill('mutability'))
const put = await node.mutablePut(mutableKeyPair, Buffer.from('a new value!'))

Note that the error throws on the bootstrap nodes, not the client node making this request. I suspect this is a silly/simple mistake or misconception on my part… which is why I came here to see if anyone has any clues. Thank you!

Node can be connected to by its defaultKeyPair.publicKey, even if `node.listening` is empty.

While testing I realized, I can connect to a node by its defaultKeyPair, even if there are no listening servers (node1.listening.size === 0, is that intended behavior? can I expect that to persist in the future, because if so, then I will expect the defaultKeyPair to be accessible as well, as a way to pass the node address, without running a server first?

    const node1 = new DHT()
    // const server = node1.createServer();
    // await server.listen();

    const node2 = new DHT()
    const secretSocket = node2.connect(node1.defaultKeyPair.publicKey)

    expect(secretSocket.remotePublicKey).to.eql(node1.defaultKeyPair.publicKey)
    expect(node1.defaultKeyPair.publicKey).to.not.eql(node2.defaultKeyPair.publicKey)
    expect(secretSocket.publicKey).to.eql(node2.defaultKeyPair.publicKey)

    node1.destroy()
    node2.destroy()

Use in browser

Are there any plans for using the dht directly on a clients browser, or is using hyperswarm-web currently the only method?

Edit: Found out about dht-relay. Does it constantly relay all the data through a middle peer, or does it only do it at the start for the initial connection?

clarification on bootstrap

Friendly message coming in, sorry for the topic though.

Having traced all modules down to dht-rpc, I find it rather apalling, that we bootstrap against 3 servers on a (privacy protected whois) domain, which does not expose the owner or contents.

Obviously this makes a DHT rather simple to use, but am I not correct to see this as a major security concern? Right now the topics seem guessable and hence it should be quite difficult to ever employ some permission system on top of it as well.

I suggest at least making that fact VERY VERY public or even rather throw an exception if an implementer does not provide an array or opt into the defaults.

Taking this one step further all related bootstrap properties should probably allow for async functions to allow for a more dynamic and / or multi way discovery, e.g. via DNS and neighbours and then also be able to validate an origin.

Kind regards

Prune the exploration branch with user functions

When a value or peer is being looked-up, at the application level (e.g. through hyper-discovery etc.) it is required to blacklist / whitelist the discovered peers and referrals.

For example, a node that is constantly giving the wrong reference values (or spoofing keys) should be banned for some time (or maybe permanently) at the app level. Or a node that is constantly trying to propagate mutable values that are failing the sign-verifications repeatedly should be banned for some time (app policy, say).

To facilitate this, if the DHT can expose a function (during the peer discovery phase) to check if a peer is allowed in the lookup process, the application can implement their own blacklist functionality to control which k-buckets can be further queried. This can efficiently prune the wrong exploration branches during the lookup itself.

Before adding a peer/node to the k-bucket, this said user function can be invoked to check if it is allowed to be added to the k-bucket (as per the app-policy).

Optional Non-encrypted streams

Some users might want to pipe encrypted streams over createServer, can we have an option to disable encryption to avoid double encrypting?

Documentation

In my opinion, the documentation (README) fails to succinctly explain what this distributed hash table is and what it is not. For instance, it does not explain how authentication and encryption is achieved. From what I read there is the concept of a topic that allows peers to discover the subset of the network that interests them. A mutable put request requires a signature to ensure that updates can only be issued by a node possessing the corresponding private key. However, the relation between this announced topic the put and get requests is not explained.
I fail to see how the network can filter what nodes are allowed to join and how the communication within the network is encrypted. Can nodes of the network discover topics by just sifting through the traffic going through them? Can eavesdroppers do the same? Can they then join topics they were not supposed to know about? Do you rely on another layer to provide authentication and encryption (VPN?). Is it sufficient to read https://en.wikipedia.org/wiki/Distributed_hash_table to understand what this package achieves? Is this DHT only suitable for public data? I am lost.

Related to #2 and #22.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.