mirror of
https://github.com/fluencelabs/js-libp2p
synced 2025-07-25 13:31:56 +00:00
Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
0e18735b8c | ||
|
f68ff35625 | ||
|
8c56ec0d23 | ||
|
fa5ee873e3 | ||
|
51d7ca44c1 | ||
|
726a746479 | ||
|
a331b84f13 | ||
|
78d152dd68 | ||
|
7e14aa19b5 | ||
|
2440c872df |
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,3 +1,29 @@
|
|||||||
|
<a name="0.28.10"></a>
|
||||||
|
## [0.28.10](https://github.com/libp2p/js-libp2p/compare/v0.28.9...v0.28.10) (2020-08-05)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* allow certain keychain operations without a password ([#726](https://github.com/libp2p/js-libp2p/issues/726)) ([8c56ec0](https://github.com/libp2p/js-libp2p/commit/8c56ec0))
|
||||||
|
* **identify:** make agentversion dynamic and add it to the peerstore ([#724](https://github.com/libp2p/js-libp2p/issues/724)) ([726a746](https://github.com/libp2p/js-libp2p/commit/726a746))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **keychain:** add support for ed25519 and secp keys ([#725](https://github.com/libp2p/js-libp2p/issues/725)) ([51d7ca4](https://github.com/libp2p/js-libp2p/commit/51d7ca4))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.28.9"></a>
|
||||||
|
## [0.28.9](https://github.com/libp2p/js-libp2p/compare/v0.28.8...v0.28.9) (2020-07-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* ping multiaddr from peer not previously stored in peerstore ([#719](https://github.com/libp2p/js-libp2p/issues/719)) ([2440c87](https://github.com/libp2p/js-libp2p/commit/2440c87))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="0.28.8"></a>
|
<a name="0.28.8"></a>
|
||||||
## [0.28.8](https://github.com/libp2p/js-libp2p/compare/v0.28.7...v0.28.8) (2020-07-20)
|
## [0.28.8](https://github.com/libp2p/js-libp2p/compare/v0.28.7...v0.28.8) (2020-07-20)
|
||||||
|
|
||||||
|
@@ -23,8 +23,8 @@
|
|||||||
<a href="https://david-dm.org/libp2p/js-libp2p"><img src="https://david-dm.org/libp2p/js-libp2p.svg?style=flat-square" /></a>
|
<a href="https://david-dm.org/libp2p/js-libp2p"><img src="https://david-dm.org/libp2p/js-libp2p.svg?style=flat-square" /></a>
|
||||||
<a href="https://github.com/feross/standard"><img src="https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square"></a>
|
<a href="https://github.com/feross/standard"><img src="https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square"></a>
|
||||||
<a href="https://github.com/RichardLitt/standard-readme"><img src="https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square" /></a>
|
<a href="https://github.com/RichardLitt/standard-readme"><img src="https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square" /></a>
|
||||||
<a href=""><img src="https://img.shields.io/badge/npm-%3E%3D3.0.0-orange.svg?style=flat-square" /></a>
|
<a href=""><img src="https://img.shields.io/badge/npm-%3E%3D6.0.0-orange.svg?style=flat-square" /></a>
|
||||||
<a href=""><img src="https://img.shields.io/badge/Node.js-%3E%3D6.0.0-orange.svg?style=flat-square" /></a>
|
<a href=""><img src="https://img.shields.io/badge/Node.js-%3E%3D10.0.0-orange.svg?style=flat-square" /></a>
|
||||||
<br>
|
<br>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
@@ -418,7 +418,7 @@ const latency = await libp2p.ping(otherPeerId)
|
|||||||
|
|
||||||
## multiaddrs
|
## multiaddrs
|
||||||
|
|
||||||
Gets the multiaddrs the libp2p node announces to the network. This computes the advertising multiaddrs
|
Gets the multiaddrs the libp2p node announces to the network. This computes the advertising multiaddrs
|
||||||
of the peer by joining the multiaddrs that libp2p transports are listening on with the announce multiaddrs
|
of the peer by joining the multiaddrs that libp2p transports are listening on with the announce multiaddrs
|
||||||
provided in the libp2p config. Configured no announce multiaddrs will be filtered out of the advertised addresses.
|
provided in the libp2p config. Configured no announce multiaddrs will be filtered out of the advertised addresses.
|
||||||
|
|
||||||
@@ -1454,7 +1454,7 @@ Create a key in the keychain.
|
|||||||
|------|------|-------------|
|
|------|------|-------------|
|
||||||
| name | `string` | The local key name. It cannot already exist. |
|
| name | `string` | The local key name. It cannot already exist. |
|
||||||
| type | `string` | One of the key types; 'rsa' |
|
| type | `string` | One of the key types; 'rsa' |
|
||||||
| size | `number` | The key size in bits. |
|
| [size] | `number` | The key size in bits. Must be provided for rsa keys. |
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
@@ -1801,7 +1801,7 @@ console.log(peerStats.toJSON())
|
|||||||
|
|
||||||
## Events
|
## Events
|
||||||
|
|
||||||
Once you have a libp2p instance, you can listen to several events it emits, so that you can be notified of relevant network events.
|
Once you have a libp2p instance, you can listen to several events it emits, so that you can be notified of relevant network events.
|
||||||
|
|
||||||
### libp2p
|
### libp2p
|
||||||
|
|
||||||
|
82
examples/pubsub/message-filtering/1.js
Normal file
82
examples/pubsub/message-filtering/1.js
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const { Buffer } = require('buffer')
|
||||||
|
const Libp2p = require('../../../')
|
||||||
|
const TCP = require('libp2p-tcp')
|
||||||
|
const Mplex = require('libp2p-mplex')
|
||||||
|
const { NOISE } = require('libp2p-noise')
|
||||||
|
const SECIO = require('libp2p-secio')
|
||||||
|
const Gossipsub = require('libp2p-gossipsub')
|
||||||
|
|
||||||
|
const createNode = async () => {
|
||||||
|
const node = await Libp2p.create({
|
||||||
|
addresses: {
|
||||||
|
listen: ['/ip4/0.0.0.0/tcp/0']
|
||||||
|
},
|
||||||
|
modules: {
|
||||||
|
transport: [TCP],
|
||||||
|
streamMuxer: [Mplex],
|
||||||
|
connEncryption: [NOISE, SECIO],
|
||||||
|
pubsub: Gossipsub
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await node.start()
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const topic = 'fruit'
|
||||||
|
|
||||||
|
const [node1, node2, node3] = await Promise.all([
|
||||||
|
createNode(),
|
||||||
|
createNode(),
|
||||||
|
createNode(),
|
||||||
|
])
|
||||||
|
|
||||||
|
// node1 conect to node2 and node2 conect to node3
|
||||||
|
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
|
||||||
|
await node1.dial(node2.peerId)
|
||||||
|
|
||||||
|
node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
|
||||||
|
await node2.dial(node3.peerId)
|
||||||
|
|
||||||
|
//subscribe
|
||||||
|
await node1.pubsub.subscribe(topic, (msg) => {
|
||||||
|
console.log(`node1 received: ${msg.data.toString()}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
await node2.pubsub.subscribe(topic, (msg) => {
|
||||||
|
console.log(`node2 received: ${msg.data.toString()}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
await node3.pubsub.subscribe(topic, (msg) => {
|
||||||
|
console.log(`node3 received: ${msg.data.toString()}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
const validateFruit = (msgTopic, peer, msg) => {
|
||||||
|
const fruit = msg.data.toString();
|
||||||
|
const validFruit = ['banana', 'apple', 'orange']
|
||||||
|
const valid = validFruit.includes(fruit);
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
//validate fruit
|
||||||
|
node1.pubsub._pubsub.topicValidators.set(topic, validateFruit);
|
||||||
|
node2.pubsub._pubsub.topicValidators.set(topic, validateFruit);
|
||||||
|
node3.pubsub._pubsub.topicValidators.set(topic, validateFruit);
|
||||||
|
|
||||||
|
// node1 publishes "fruits" every five seconds
|
||||||
|
var count = 0;
|
||||||
|
const myFruits = ['banana', 'apple', 'car', 'orange'];
|
||||||
|
// car is not a fruit !
|
||||||
|
setInterval(() => {
|
||||||
|
console.log('############## fruit ' + myFruits[count] + ' ##############')
|
||||||
|
node1.pubsub.publish(topic, Buffer.from(myFruits[count]))
|
||||||
|
count++
|
||||||
|
if (count == myFruits.length) {
|
||||||
|
count = 0
|
||||||
|
}
|
||||||
|
}, 5000)
|
||||||
|
})()
|
108
examples/pubsub/message-filtering/README.md
Normal file
108
examples/pubsub/message-filtering/README.md
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
# Filter Messages
|
||||||
|
|
||||||
|
To prevent undesired data from being propagated on the network, we can apply a filter to Gossipsub. Messages that fail validation in the filter will not be re-shared.
|
||||||
|
|
||||||
|
## 1. Setting up a PubSub network with three nodes
|
||||||
|
|
||||||
|
First, let's update our libp2p configuration with a pubsub implementation.
|
||||||
|
|
||||||
|
```JavaScript
|
||||||
|
const Libp2p = require('libp2p')
|
||||||
|
const Gossipsub = require('libp2p-gossipsub')
|
||||||
|
|
||||||
|
const node = await Libp2p.create({
|
||||||
|
addresses: {
|
||||||
|
listen: ['/ip4/0.0.0.0/tcp/0']
|
||||||
|
},
|
||||||
|
modules: {
|
||||||
|
transport: [ TCP ],
|
||||||
|
streamMuxer: [ Mplex ],
|
||||||
|
connEncryption: [ NOISE, SECIO ],
|
||||||
|
pubsub: Gossipsub
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, create three nodes and connect them together. In this example, we will connect the nodes in series. Node 1 connected with node 2 and node 2 connected with node 3.
|
||||||
|
|
||||||
|
```JavaScript
|
||||||
|
const [node1, node2, node3] = await Promise.all([
|
||||||
|
createNode(),
|
||||||
|
createNode(),
|
||||||
|
createNode(),
|
||||||
|
])
|
||||||
|
|
||||||
|
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
|
||||||
|
await node1.dial(node2.peerId)
|
||||||
|
|
||||||
|
node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
|
||||||
|
await node2.dial(node3.peerId)
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we' can subscribe to the fruit topic and log incoming messages.
|
||||||
|
|
||||||
|
```JavaScript
|
||||||
|
const topic = 'fruit'
|
||||||
|
|
||||||
|
await node1.pubsub.subscribe(topic, (msg) => {
|
||||||
|
console.log(`node1 received: ${msg.data.toString()}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
await node2.pubsub.subscribe(topic, (msg) => {
|
||||||
|
console.log(`node2 received: ${msg.data.toString()}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
await node3.pubsub.subscribe(topic, (msg) => {
|
||||||
|
console.log(`node3 received: ${msg.data.toString()}`)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
Finally, let's define the additional filter in the fruit topic.
|
||||||
|
|
||||||
|
```JavaScript
|
||||||
|
const validateFruit = (msgTopic, peer, msg) => {
|
||||||
|
const fruit = msg.data.toString();
|
||||||
|
const validFruit = ['banana', 'apple', 'orange']
|
||||||
|
const valid = validFruit.includes(fruit);
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
node1.pubsub._pubsub.topicValidators.set(topic, validateFruit);
|
||||||
|
node2.pubsub._pubsub.topicValidators.set(topic, validateFruit);
|
||||||
|
node3.pubsub._pubsub.topicValidators.set(topic, validateFruit);
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, node one has an outdated version of the system, or is a malicious node. When it tries to publish fruit, the messages are re-shared and all the nodes share the message. However, when it tries to publish a vehicle the message is not re-shared.
|
||||||
|
|
||||||
|
```JavaScript
|
||||||
|
var count = 0;
|
||||||
|
const myFruits = ['banana', 'apple', 'car', 'orange'];
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
console.log('############## fruit ' + myFruits[count] + ' ##############')
|
||||||
|
node1.pubsub.publish(topic, Buffer.from(myFruits[count]))
|
||||||
|
count++
|
||||||
|
if (count == myFruits.length) {
|
||||||
|
count = 0
|
||||||
|
}
|
||||||
|
}, 5000)
|
||||||
|
```
|
||||||
|
|
||||||
|
Result
|
||||||
|
|
||||||
|
```
|
||||||
|
> node 1.js
|
||||||
|
############## fruit banana ##############
|
||||||
|
node1 received: banana
|
||||||
|
node2 received: banana
|
||||||
|
node3 received: banana
|
||||||
|
############## fruit apple ##############
|
||||||
|
node1 received: apple
|
||||||
|
node2 received: apple
|
||||||
|
node3 received: apple
|
||||||
|
############## fruit car ##############
|
||||||
|
node1 received: car
|
||||||
|
############## fruit orange ##############
|
||||||
|
node1 received: orange
|
||||||
|
node2 received: orange
|
||||||
|
node3 received: orange
|
||||||
|
```
|
53
package.json
53
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "libp2p",
|
"name": "libp2p",
|
||||||
"version": "0.28.8",
|
"version": "0.28.10",
|
||||||
"description": "JavaScript implementation of libp2p, a modular peer to peer network stack",
|
"description": "JavaScript implementation of libp2p, a modular peer to peer network stack",
|
||||||
"leadMaintainer": "Jacob Heun <jacobheun@gmail.com>",
|
"leadMaintainer": "Jacob Heun <jacobheun@gmail.com>",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
"it-length-prefixed": "^3.0.1",
|
"it-length-prefixed": "^3.0.1",
|
||||||
"it-pipe": "^1.1.0",
|
"it-pipe": "^1.1.0",
|
||||||
"it-protocol-buffers": "^0.2.0",
|
"it-protocol-buffers": "^0.2.0",
|
||||||
"libp2p-crypto": "^0.17.8",
|
"libp2p-crypto": "^0.17.9",
|
||||||
"libp2p-interfaces": "^0.3.1",
|
"libp2p-interfaces": "^0.3.1",
|
||||||
"libp2p-utils": "^0.1.2",
|
"libp2p-utils": "^0.1.2",
|
||||||
"mafmt": "^7.0.0",
|
"mafmt": "^7.0.0",
|
||||||
@@ -126,43 +126,44 @@
|
|||||||
"Vasco Santos <vasco.santos@moxy.studio>",
|
"Vasco Santos <vasco.santos@moxy.studio>",
|
||||||
"Alan Shaw <alan@tableflip.io>",
|
"Alan Shaw <alan@tableflip.io>",
|
||||||
"Cayman <caymannava@gmail.com>",
|
"Cayman <caymannava@gmail.com>",
|
||||||
|
"Alex Potsides <alex@achingbrain.net>",
|
||||||
"Pedro Teixeira <i@pgte.me>",
|
"Pedro Teixeira <i@pgte.me>",
|
||||||
"Friedel Ziegelmayer <dignifiedquire@gmail.com>",
|
"Friedel Ziegelmayer <dignifiedquire@gmail.com>",
|
||||||
"Alex Potsides <alex@achingbrain.net>",
|
|
||||||
"Maciej Krüger <mkg20001@gmail.com>",
|
"Maciej Krüger <mkg20001@gmail.com>",
|
||||||
"Hugo Dias <mail@hugodias.me>",
|
"Hugo Dias <mail@hugodias.me>",
|
||||||
"Volker Mische <volker.mische@gmail.com>",
|
|
||||||
"dirkmc <dirkmdev@gmail.com>",
|
"dirkmc <dirkmdev@gmail.com>",
|
||||||
|
"Volker Mische <volker.mische@gmail.com>",
|
||||||
"Richard Littauer <richard.littauer@gmail.com>",
|
"Richard Littauer <richard.littauer@gmail.com>",
|
||||||
"Thomas Eizinger <thomas@eizinger.io>",
|
|
||||||
"Ryan Bell <ryan@piing.net>",
|
|
||||||
"Giovanni T. Parra <fiatjaf@gmail.com>",
|
|
||||||
"Andrew Nesbitt <andrewnez@gmail.com>",
|
|
||||||
"ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>",
|
"ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>",
|
||||||
|
"Andrew Nesbitt <andrewnez@gmail.com>",
|
||||||
"Elven <mon.samuel@qq.com>",
|
"Elven <mon.samuel@qq.com>",
|
||||||
"Didrik Nordström <didrik.nordstrom@gmail.com>",
|
"Giovanni T. Parra <fiatjaf@gmail.com>",
|
||||||
"Tiago Alves <alvesjtiago@gmail.com>",
|
"Ryan Bell <ryan@piing.net>",
|
||||||
"Yusef Napora <yusef@napora.org>",
|
"Thomas Eizinger <thomas@eizinger.io>",
|
||||||
"Zane Starr <zcstarr@gmail.com>",
|
"Didrik Nordström <didrik@betamos.se>",
|
||||||
"ebinks <elizabethjbinks@gmail.com>",
|
|
||||||
"isan_rivkin <isanrivkin@gmail.com>",
|
|
||||||
"robertkiel <robert.kiel@validitylabs.org>",
|
|
||||||
"RasmusErik Voel Jensen <github@solsort.com>",
|
|
||||||
"Bernd Strehl <bernd.strehl@gmail.com>",
|
|
||||||
"Chris Bratlien <chrisbratlien@gmail.com>",
|
|
||||||
"Daijiro Wachi <daijiro.wachi@gmail.com>",
|
|
||||||
"Diogo Silva <fsdiogo@gmail.com>",
|
|
||||||
"Dmitriy Ryajov <dryajov@gmail.com>",
|
|
||||||
"Fei Liu <liu.feiwood@gmail.com>",
|
|
||||||
"Florian-Merle <florian.david.merle@gmail.com>",
|
|
||||||
"Francis Gulotta <wizard@roborooter.com>",
|
"Francis Gulotta <wizard@roborooter.com>",
|
||||||
"Henrique Dias <hacdias@gmail.com>",
|
"Florian-Merle <florian.david.merle@gmail.com>",
|
||||||
"Irakli Gozalishvili <rfobic@gmail.com>",
|
|
||||||
"Joel Gustafson <joelg@mit.edu>",
|
"Joel Gustafson <joelg@mit.edu>",
|
||||||
"Julien Bouquillon <contact@revolunet.com>",
|
"Julien Bouquillon <contact@revolunet.com>",
|
||||||
"Kevin Kwok <antimatter15@gmail.com>",
|
"Kevin Kwok <antimatter15@gmail.com>",
|
||||||
|
"Felipe Martins <felipebrasil93@gmail.com>",
|
||||||
"Nuno Nogueira <nunofmn@gmail.com>",
|
"Nuno Nogueira <nunofmn@gmail.com>",
|
||||||
|
"Fei Liu <liu.feiwood@gmail.com>",
|
||||||
|
"RasmusErik Voel Jensen <github@solsort.com>",
|
||||||
|
"Dmitriy Ryajov <dryajov@gmail.com>",
|
||||||
"Soeren <nikorpoulsen@gmail.com>",
|
"Soeren <nikorpoulsen@gmail.com>",
|
||||||
"Sönke Hahn <soenkehahn@gmail.com>"
|
"Sönke Hahn <soenkehahn@gmail.com>",
|
||||||
|
"Tiago Alves <alvesjtiago@gmail.com>",
|
||||||
|
"Diogo Silva <fsdiogo@gmail.com>",
|
||||||
|
"Yusef Napora <yusef@napora.org>",
|
||||||
|
"Zane Starr <zcstarr@gmail.com>",
|
||||||
|
"Daijiro Wachi <daijiro.wachi@gmail.com>",
|
||||||
|
"Chris Bratlien <chrisbratlien@gmail.com>",
|
||||||
|
"ebinks <elizabethjbinks@gmail.com>",
|
||||||
|
"Bernd Strehl <bernd.strehl@gmail.com>",
|
||||||
|
"isan_rivkin <isanrivkin@gmail.com>",
|
||||||
|
"Henrique Dias <hacdias@gmail.com>",
|
||||||
|
"robertkiel <robert.kiel@validitylabs.org>",
|
||||||
|
"Irakli Gozalishvili <rfobic@gmail.com>"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
|
const libp2pVersion = require('../../package.json').version
|
||||||
|
|
||||||
module.exports.PROTOCOL_VERSION = 'ipfs/0.1.0'
|
module.exports.PROTOCOL_VERSION = 'ipfs/0.1.0'
|
||||||
module.exports.AGENT_VERSION = 'js-libp2p/0.1.0'
|
module.exports.AGENT_VERSION = `js-libp2p/${libp2pVersion}`
|
||||||
module.exports.MULTICODEC_IDENTIFY = '/ipfs/id/1.0.0'
|
module.exports.MULTICODEC_IDENTIFY = '/ipfs/id/1.0.0'
|
||||||
module.exports.MULTICODEC_IDENTIFY_PUSH = '/ipfs/id/push/1.0.0'
|
module.exports.MULTICODEC_IDENTIFY_PUSH = '/ipfs/id/push/1.0.0'
|
||||||
|
@@ -175,6 +175,7 @@ class IdentifyService {
|
|||||||
// Update peers data in PeerStore
|
// Update peers data in PeerStore
|
||||||
this.peerStore.addressBook.set(id, listenAddrs.map((addr) => multiaddr(addr)))
|
this.peerStore.addressBook.set(id, listenAddrs.map((addr) => multiaddr(addr)))
|
||||||
this.peerStore.protoBook.set(id, protocols)
|
this.peerStore.protoBook.set(id, protocols)
|
||||||
|
this.peerStore.metadataBook.set(id, 'AgentVersion', Buffer.from(message.agentVersion))
|
||||||
|
|
||||||
// TODO: Track our observed address so that we can score it
|
// TODO: Track our observed address so that we can score it
|
||||||
log('received observed address of %s', observedAddr)
|
log('received observed address of %s', observedAddr)
|
||||||
|
11
src/index.js
11
src/index.js
@@ -82,7 +82,7 @@ class Libp2p extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create keychain
|
// Create keychain
|
||||||
if (this._options.keychain && this._options.keychain.pass && this._options.keychain.datastore) {
|
if (this._options.keychain && this._options.keychain.datastore) {
|
||||||
log('creating keychain')
|
log('creating keychain')
|
||||||
|
|
||||||
const keychainOpts = Keychain.generateOptions()
|
const keychainOpts = Keychain.generateOptions()
|
||||||
@@ -324,7 +324,7 @@ class Libp2p extends EventEmitter {
|
|||||||
* @returns {Promise<Connection|*>}
|
* @returns {Promise<Connection|*>}
|
||||||
*/
|
*/
|
||||||
async dialProtocol (peer, protocols, options) {
|
async dialProtocol (peer, protocols, options) {
|
||||||
const { id, multiaddrs } = getPeer(peer, this.peerStore)
|
const { id, multiaddrs } = getPeer(peer)
|
||||||
let connection = this.connectionManager.get(id)
|
let connection = this.connectionManager.get(id)
|
||||||
|
|
||||||
if (!connection) {
|
if (!connection) {
|
||||||
@@ -396,7 +396,12 @@ class Libp2p extends EventEmitter {
|
|||||||
* @returns {Promise<number>}
|
* @returns {Promise<number>}
|
||||||
*/
|
*/
|
||||||
ping (peer) {
|
ping (peer) {
|
||||||
const { id } = getPeer(peer)
|
const { id, multiaddrs } = getPeer(peer)
|
||||||
|
|
||||||
|
// If received multiaddr, ping it
|
||||||
|
if (multiaddrs) {
|
||||||
|
return ping(this, multiaddrs[0])
|
||||||
|
}
|
||||||
|
|
||||||
return ping(this, id)
|
return ping(this, id)
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,9 @@ const crypto = require('libp2p-crypto')
|
|||||||
const DS = require('interface-datastore')
|
const DS = require('interface-datastore')
|
||||||
const CMS = require('./cms')
|
const CMS = require('./cms')
|
||||||
const errcode = require('err-code')
|
const errcode = require('err-code')
|
||||||
|
const { Number } = require('ipfs-utils/src/globalthis')
|
||||||
|
|
||||||
|
require('node-forge/lib/sha512')
|
||||||
|
|
||||||
const keyPrefix = '/pkcs8/'
|
const keyPrefix = '/pkcs8/'
|
||||||
const infoPrefix = '/info/'
|
const infoPrefix = '/info/'
|
||||||
@@ -107,7 +110,7 @@ class Keychain {
|
|||||||
this.opts = mergeOptions(defaultOptions, options)
|
this.opts = mergeOptions(defaultOptions, options)
|
||||||
|
|
||||||
// Enforce NIST SP 800-132
|
// Enforce NIST SP 800-132
|
||||||
if (!this.opts.passPhrase || this.opts.passPhrase.length < 20) {
|
if (this.opts.passPhrase && this.opts.passPhrase.length < 20) {
|
||||||
throw new Error('passPhrase must be least 20 characters')
|
throw new Error('passPhrase must be least 20 characters')
|
||||||
}
|
}
|
||||||
if (this.opts.dek.keyLength < NIST.minKeyLength) {
|
if (this.opts.dek.keyLength < NIST.minKeyLength) {
|
||||||
@@ -120,13 +123,13 @@ class Keychain {
|
|||||||
throw new Error(`dek.iterationCount must be least ${NIST.minIterationCount}`)
|
throw new Error(`dek.iterationCount must be least ${NIST.minIterationCount}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the derived encrypting key
|
const dek = this.opts.passPhrase ? crypto.pbkdf2(
|
||||||
const dek = crypto.pbkdf2(
|
|
||||||
this.opts.passPhrase,
|
this.opts.passPhrase,
|
||||||
this.opts.dek.salt,
|
this.opts.dek.salt,
|
||||||
this.opts.dek.iterationCount,
|
this.opts.dek.iterationCount,
|
||||||
this.opts.dek.keyLength,
|
this.opts.dek.keyLength,
|
||||||
this.opts.dek.hash)
|
this.opts.dek.hash) : ''
|
||||||
|
|
||||||
Object.defineProperty(this, '_', { value: () => dek })
|
Object.defineProperty(this, '_', { value: () => dek })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,8 +174,8 @@ class Keychain {
|
|||||||
*
|
*
|
||||||
* @param {string} name - The local key name; cannot already exist.
|
* @param {string} name - The local key name; cannot already exist.
|
||||||
* @param {string} type - One of the key types; 'rsa'.
|
* @param {string} type - One of the key types; 'rsa'.
|
||||||
* @param {int} size - The key size in bits.
|
* @param {int} [size] - The key size in bits. Used for rsa keys only.
|
||||||
* @returns {KeyInfo}
|
* @returns {KeyInfo}
|
||||||
*/
|
*/
|
||||||
async createKey (name, type, size) {
|
async createKey (name, type, size) {
|
||||||
const self = this
|
const self = this
|
||||||
@@ -185,17 +188,13 @@ class Keychain {
|
|||||||
return throwDelayed(errcode(new Error(`Invalid key type '${type}'`), 'ERR_INVALID_KEY_TYPE'))
|
return throwDelayed(errcode(new Error(`Invalid key type '${type}'`), 'ERR_INVALID_KEY_TYPE'))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Number.isSafeInteger(size)) {
|
|
||||||
return throwDelayed(errcode(new Error(`Invalid key size '${size}'`), 'ERR_INVALID_KEY_SIZE'))
|
|
||||||
}
|
|
||||||
|
|
||||||
const dsname = DsName(name)
|
const dsname = DsName(name)
|
||||||
const exists = await self.store.has(dsname)
|
const exists = await self.store.has(dsname)
|
||||||
if (exists) return throwDelayed(errcode(new Error(`Key '${name}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
|
if (exists) return throwDelayed(errcode(new Error(`Key '${name}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
|
||||||
|
|
||||||
switch (type.toLowerCase()) {
|
switch (type.toLowerCase()) {
|
||||||
case 'rsa':
|
case 'rsa':
|
||||||
if (size < 2048) {
|
if (!Number.isSafeInteger(size) || size < 2048) {
|
||||||
return throwDelayed(errcode(new Error(`Invalid RSA key size ${size}`), 'ERR_INVALID_KEY_SIZE'))
|
return throwDelayed(errcode(new Error(`Invalid RSA key size ${size}`), 'ERR_INVALID_KEY_SIZE'))
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
@@ -15,11 +15,11 @@ const { PROTOCOL, PING_LENGTH } = require('./constants')
|
|||||||
/**
|
/**
|
||||||
* Ping a given peer and wait for its response, getting the operation latency.
|
* Ping a given peer and wait for its response, getting the operation latency.
|
||||||
* @param {Libp2p} node
|
* @param {Libp2p} node
|
||||||
* @param {PeerId} peer
|
* @param {PeerId|multiaddr} peer
|
||||||
* @returns {Promise<Number>}
|
* @returns {Promise<Number>}
|
||||||
*/
|
*/
|
||||||
async function ping (node, peer) {
|
async function ping (node, peer) {
|
||||||
log('dialing %s to %s', PROTOCOL, peer.toB58String())
|
log('dialing %s to %s', PROTOCOL, peer.toB58String ? peer.toB58String() : peer)
|
||||||
|
|
||||||
const { stream } = await node.dialProtocol(peer, PROTOCOL)
|
const { stream } = await node.dialProtocol(peer, PROTOCOL)
|
||||||
|
|
||||||
|
@@ -17,7 +17,7 @@ describe('ping', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
nodes = await peerUtils.createPeer({
|
nodes = await peerUtils.createPeer({
|
||||||
number: 2,
|
number: 3,
|
||||||
config: baseOptions
|
config: baseOptions
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -25,7 +25,14 @@ describe('ping', () => {
|
|||||||
nodes[1].peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs)
|
nodes[1].peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('ping once from peer0 to peer1', async () => {
|
it('ping once from peer0 to peer1 using a multiaddr', async () => {
|
||||||
|
const ma = `${nodes[2].multiaddrs[0]}/p2p/${nodes[2].peerId.toB58String()}`
|
||||||
|
const latency = await nodes[0].ping(ma)
|
||||||
|
|
||||||
|
expect(latency).to.be.a('Number')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('ping once from peer0 to peer1 using a peerId', async () => {
|
||||||
const latency = await nodes[0].ping(nodes[1].peerId)
|
const latency = await nodes[0].ping(nodes[1].peerId)
|
||||||
|
|
||||||
expect(latency).to.be.a('Number')
|
expect(latency).to.be.a('Number')
|
||||||
|
@@ -19,6 +19,7 @@ const { IdentifyService, multicodecs } = require('../../src/identify')
|
|||||||
const Peers = require('../fixtures/peers')
|
const Peers = require('../fixtures/peers')
|
||||||
const Libp2p = require('../../src')
|
const Libp2p = require('../../src')
|
||||||
const baseOptions = require('../utils/base-options.browser')
|
const baseOptions = require('../utils/base-options.browser')
|
||||||
|
const pkg = require('../../package.json')
|
||||||
|
|
||||||
const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser')
|
const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser')
|
||||||
const remoteAddr = MULTIADDRS_WEBSOCKETS[0]
|
const remoteAddr = MULTIADDRS_WEBSOCKETS[0]
|
||||||
@@ -53,6 +54,9 @@ describe('Identify', () => {
|
|||||||
},
|
},
|
||||||
protoBook: {
|
protoBook: {
|
||||||
set: () => { }
|
set: () => { }
|
||||||
|
},
|
||||||
|
metadataBook: {
|
||||||
|
set: () => { }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
multiaddrs: []
|
multiaddrs: []
|
||||||
@@ -77,6 +81,7 @@ describe('Identify', () => {
|
|||||||
|
|
||||||
sinon.spy(localIdentify.peerStore.addressBook, 'set')
|
sinon.spy(localIdentify.peerStore.addressBook, 'set')
|
||||||
sinon.spy(localIdentify.peerStore.protoBook, 'set')
|
sinon.spy(localIdentify.peerStore.protoBook, 'set')
|
||||||
|
sinon.spy(localIdentify.peerStore.metadataBook, 'set')
|
||||||
|
|
||||||
// Run identify
|
// Run identify
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@@ -90,6 +95,12 @@ describe('Identify', () => {
|
|||||||
|
|
||||||
expect(localIdentify.peerStore.addressBook.set.callCount).to.equal(1)
|
expect(localIdentify.peerStore.addressBook.set.callCount).to.equal(1)
|
||||||
expect(localIdentify.peerStore.protoBook.set.callCount).to.equal(1)
|
expect(localIdentify.peerStore.protoBook.set.callCount).to.equal(1)
|
||||||
|
|
||||||
|
const metadataArgs = localIdentify.peerStore.metadataBook.set.firstCall.args
|
||||||
|
expect(metadataArgs[0].id.bytes).to.equal(remotePeer.bytes)
|
||||||
|
expect(metadataArgs[1]).to.equal('AgentVersion')
|
||||||
|
expect(metadataArgs[2].toString()).to.equal(`js-libp2p/${pkg.version}`)
|
||||||
|
|
||||||
// Validate the remote peer gets updated in the peer store
|
// Validate the remote peer gets updated in the peer store
|
||||||
const call = localIdentify.peerStore.addressBook.set.firstCall
|
const call = localIdentify.peerStore.addressBook.set.firstCall
|
||||||
expect(call.args[0].id.bytes).to.equal(remotePeer.bytes)
|
expect(call.args[0].id.bytes).to.equal(remotePeer.bytes)
|
||||||
|
@@ -2,10 +2,8 @@
|
|||||||
/* eslint-env mocha */
|
/* eslint-env mocha */
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const chai = require('chai')
|
const { chai, expect } = require('aegir/utils/chai')
|
||||||
const { expect } = chai
|
|
||||||
const fail = expect.fail
|
const fail = expect.fail
|
||||||
chai.use(require('dirty-chai'))
|
|
||||||
chai.use(require('chai-string'))
|
chai.use(require('chai-string'))
|
||||||
|
|
||||||
const peerUtils = require('../utils/creators/peer')
|
const peerUtils = require('../utils/creators/peer')
|
||||||
@@ -40,8 +38,8 @@ describe('keychain', () => {
|
|||||||
emptyKeystore = new Keychain(datastore1, { passPhrase: passPhrase })
|
emptyKeystore = new Keychain(datastore1, { passPhrase: passPhrase })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('needs a pass phrase to encrypt a key', () => {
|
it('can start without a password', () => {
|
||||||
expect(() => new Keychain(datastore2)).to.throw()
|
expect(() => new Keychain(datastore2)).to.not.throw()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('needs a NIST SP 800-132 non-weak pass phrase', () => {
|
it('needs a NIST SP 800-132 non-weak pass phrase', () => {
|
||||||
@@ -56,12 +54,48 @@ describe('keychain', () => {
|
|||||||
expect(Keychain.options).to.exist()
|
expect(Keychain.options).to.exist()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('needs a supported hashing alorithm', () => {
|
it('supports supported hashing alorithms', () => {
|
||||||
const ok = new Keychain(datastore2, { passPhrase: passPhrase, dek: { hash: 'sha2-256' } })
|
const ok = new Keychain(datastore2, { passPhrase: passPhrase, dek: { hash: 'sha2-256' } })
|
||||||
expect(ok).to.exist()
|
expect(ok).to.exist()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not support unsupported hashing alorithms', () => {
|
||||||
expect(() => new Keychain(datastore2, { passPhrase: passPhrase, dek: { hash: 'my-hash' } })).to.throw()
|
expect(() => new Keychain(datastore2, { passPhrase: passPhrase, dek: { hash: 'my-hash' } })).to.throw()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('can list keys without a password', async () => {
|
||||||
|
const keychain = new Keychain(datastore2)
|
||||||
|
|
||||||
|
expect(await keychain.listKeys()).to.have.lengthOf(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can find a key without a password', async () => {
|
||||||
|
const keychain = new Keychain(datastore2)
|
||||||
|
const keychainWithPassword = new Keychain(datastore2, { passPhrase: `hello-${Date.now()}-${Date.now()}` })
|
||||||
|
const id = `key-${Math.random()}`
|
||||||
|
|
||||||
|
await keychainWithPassword.createKey(id, 'rsa', 2048)
|
||||||
|
|
||||||
|
await expect(keychain.findKeyById(id)).to.eventually.be.ok()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can remove a key without a password', async () => {
|
||||||
|
const keychainWithoutPassword = new Keychain(datastore2)
|
||||||
|
const keychainWithPassword = new Keychain(datastore2, { passPhrase: `hello-${Date.now()}-${Date.now()}` })
|
||||||
|
const name = `key-${Math.random()}`
|
||||||
|
|
||||||
|
expect(await keychainWithPassword.createKey(name, 'rsa', 2048)).to.have.property('name', name)
|
||||||
|
expect(await keychainWithoutPassword.findKeyByName(name)).to.have.property('name', name)
|
||||||
|
await keychainWithoutPassword.removeKey(name)
|
||||||
|
await expect(keychainWithoutPassword.findKeyByName(name)).to.be.rejectedWith(/does not exist/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('requires a key to create a password', async () => {
|
||||||
|
const keychain = new Keychain(datastore2)
|
||||||
|
|
||||||
|
await expect(keychain.createKey('derp')).to.be.rejected()
|
||||||
|
})
|
||||||
|
|
||||||
it('can generate options', () => {
|
it('can generate options', () => {
|
||||||
const options = Keychain.generateOptions()
|
const options = Keychain.generateOptions()
|
||||||
options.passPhrase = passPhrase
|
options.passPhrase = passPhrase
|
||||||
@@ -149,6 +183,70 @@ describe('keychain', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('ed25519 keys', () => {
|
||||||
|
const keyName = 'my custom key'
|
||||||
|
it('can be an ed25519 key', async () => {
|
||||||
|
const keyInfo = await ks.createKey(keyName, 'ed25519')
|
||||||
|
expect(keyInfo).to.exist()
|
||||||
|
expect(keyInfo).to.have.property('name', keyName)
|
||||||
|
expect(keyInfo).to.have.property('id')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not overwrite existing key', async () => {
|
||||||
|
const err = await ks.createKey(keyName, 'ed25519').then(fail, err => err)
|
||||||
|
expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can export/import a key', async () => {
|
||||||
|
const keyName = 'a new key'
|
||||||
|
const password = 'my sneaky password'
|
||||||
|
const keyInfo = await ks.createKey(keyName, 'ed25519')
|
||||||
|
const exportedKey = await ks.exportKey(keyName, password)
|
||||||
|
// remove it so we can import it
|
||||||
|
await ks.removeKey(keyName)
|
||||||
|
const importedKey = await ks.importKey(keyName, exportedKey, password)
|
||||||
|
expect(importedKey.id).to.eql(keyInfo.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('cannot create the "self" key', async () => {
|
||||||
|
const err = await ks.createKey('self', 'ed25519').then(fail, err => err)
|
||||||
|
expect(err).to.exist()
|
||||||
|
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('secp256k1 keys', () => {
|
||||||
|
const keyName = 'my secp256k1 key'
|
||||||
|
it('can be an secp256k1 key', async () => {
|
||||||
|
const keyInfo = await ks.createKey(keyName, 'secp256k1')
|
||||||
|
expect(keyInfo).to.exist()
|
||||||
|
expect(keyInfo).to.have.property('name', keyName)
|
||||||
|
expect(keyInfo).to.have.property('id')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not overwrite existing key', async () => {
|
||||||
|
const err = await ks.createKey(keyName, 'secp256k1').then(fail, err => err)
|
||||||
|
expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can export/import a key', async () => {
|
||||||
|
const keyName = 'a new secp256k1 key'
|
||||||
|
const password = 'my sneaky password'
|
||||||
|
const keyInfo = await ks.createKey(keyName, 'secp256k1')
|
||||||
|
const exportedKey = await ks.exportKey(keyName, password)
|
||||||
|
// remove it so we can import it
|
||||||
|
await ks.removeKey(keyName)
|
||||||
|
const importedKey = await ks.importKey(keyName, exportedKey, password)
|
||||||
|
expect(importedKey.id).to.eql(keyInfo.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('cannot create the "self" key', async () => {
|
||||||
|
const err = await ks.createKey('self', 'secp256k1').then(fail, err => err)
|
||||||
|
expect(err).to.exist()
|
||||||
|
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('query', () => {
|
describe('query', () => {
|
||||||
it('finds all existing keys', async () => {
|
it('finds all existing keys', async () => {
|
||||||
const keys = await ks.listKeys()
|
const keys = await ks.listKeys()
|
||||||
@@ -411,7 +509,7 @@ describe('libp2p.keychain', () => {
|
|||||||
throw new Error('should throw an error using the keychain if no passphrase provided')
|
throw new Error('should throw an error using the keychain if no passphrase provided')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can be used if a passphrase is provided', async () => {
|
it('can be used when a passphrase is provided', async () => {
|
||||||
const [libp2p] = await peerUtils.createPeer({
|
const [libp2p] = await peerUtils.createPeer({
|
||||||
started: false,
|
started: false,
|
||||||
config: {
|
config: {
|
||||||
@@ -428,6 +526,22 @@ describe('libp2p.keychain', () => {
|
|||||||
expect(kInfo).to.exist()
|
expect(kInfo).to.exist()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('does not require a keychain passphrase', async () => {
|
||||||
|
const [libp2p] = await peerUtils.createPeer({
|
||||||
|
started: false,
|
||||||
|
config: {
|
||||||
|
keychain: {
|
||||||
|
datastore: new MemoryDatastore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await libp2p.loadKeychain()
|
||||||
|
|
||||||
|
const kInfo = await libp2p.keychain.createKey('keyName', 'ed25519')
|
||||||
|
expect(kInfo).to.exist()
|
||||||
|
})
|
||||||
|
|
||||||
it('can reload keys', async () => {
|
it('can reload keys', async () => {
|
||||||
const datastore = new MemoryDatastore()
|
const datastore = new MemoryDatastore()
|
||||||
const [libp2p] = await peerUtils.createPeer({
|
const [libp2p] = await peerUtils.createPeer({
|
||||||
|
Reference in New Issue
Block a user