Skip to content

Commit 2920136

Browse files
authored
fix!: remove private key field from peer id (#2660)
The only time you should ever see a private key on a `PeerId` is for the id of the currently running node. An exception to this is the keychain which used `PeerId`s to export private keys, but a nicer API there is to just deal with `PrivateKey` instances directly. At runtime a service can obtain the unmarshaled private key by adding a `privateKey: PrivateKey` field to it's components map so there's no need to have the field on every `PeerId`. The `publicKey` field of a `PeerId` was a `Uint8Array` which (if present) held the public key bytes in a protobuf wrapper along with a field saying what type of key it was. This was because we wanted to avoid pulling `@libp2p/crypto` into front ends, since it had a dependency on `node-forge` which was a large blob of untreeshakable CSJ code. This dependency has been removed so `@libp2p/crypto` is now comparatively lightweight so we can use the `PublicKey` type instead of `Uint8Array`, which also saves CPU time since we don't need to unmarshal the key before we can use it. Fixes #2659 BREAKING CHANGE: - The `.privateKey` field of the `PeerId` interface has been removed - The `.publicKey` field of the `PeerId` interface is now a `PublicKey` instead of a `Uint8Array` - `createLibp2p` now accepts a `privateKey` instead of a `peerId` - The keychain operates on `PrivateKey` instances instead of `PeerId`s with `.privateKey` fields - `@libp2p/peer-id-factory` has been removed, use `generateKeyPair` and `peerIdFromPrivateKey` instead
1 parent b0aa059 commit 2920136

File tree

303 files changed

+11826
-6453
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

303 files changed

+11826
-6453
lines changed

.github/workflows/examples.yml

+35-35
Original file line numberDiff line numberDiff line change
@@ -19,38 +19,38 @@ jobs:
1919
node-version: lts/*
2020
- uses: ipfs/aegir/actions/cache-node-modules@master
2121

22-
test-examples:
23-
name: Test example ${{ matrix.example.name }}
24-
runs-on: ubuntu-latest
25-
needs: build
26-
continue-on-error: true
27-
strategy:
28-
matrix:
29-
example:
30-
- name: js-libp2p-example-chat
31-
repo: https://github.com/libp2p/js-libp2p-example-chat.git
32-
deps:
33-
- '@libp2p/peer-id-factory@$PWD/packages/peer-id-factory'
34-
- '@libp2p/tcp@$PWD/packages/transport-tcp'
35-
- '@libp2p/websockets@$PWD/packages/transport-websockets'
36-
- 'libp2p@$PWD/packages/libp2p'
37-
- name: js-libp2p-example-circuit-relay
38-
repo: https://github.com/libp2p/js-libp2p-example-circuit-relay.git
39-
deps:
40-
- '@libp2p/circuit-relay-v2@$PWD/packages/transport-circuit-relay-v2'
41-
- '@libp2p/identify@$PWD/packages/protocol-identify'
42-
- '@libp2p/websockets@$PWD/packages/transport-websockets'
43-
- 'libp2p@$PWD/packages/libp2p'
44-
- name: js-libp2p-example-connection-encryption
45-
repo: https://github.com/libp2p/js-libp2p-example-connection-encryption.git
46-
deps:
47-
- '@libp2p/plaintext@$PWD/packages/connection-encrypter-plaintext'
48-
- '@libp2p/tcp@$PWD/packages/transport-tcp'
49-
- 'libp2p@$PWD/packages/libp2p'
50-
steps:
51-
- uses: actions/checkout@v4
52-
- uses: actions/setup-node@v4
53-
with:
54-
node-version: lts/*
55-
- uses: ipfs/aegir/actions/cache-node-modules@master
56-
- run: npx xvfb-maybe aegir test-dependant ${{ matrix.example.repo }} --deps ${{ join(matrix.example.deps, ',') }}
22+
# re-enable after libp2p@2.x.x release
23+
# test-examples:
24+
# name: Test example ${{ matrix.example.name }}
25+
# runs-on: ubuntu-latest
26+
# needs: build
27+
# continue-on-error: true
28+
# strategy:
29+
# matrix:
30+
# example:
31+
# - name: js-libp2p-example-chat
32+
# repo: https://github.com/libp2p/js-libp2p-example-chat.git
33+
# deps:
34+
# - '@libp2p/tcp@$PWD/packages/transport-tcp'
35+
# - '@libp2p/websockets@$PWD/packages/transport-websockets'
36+
# - 'libp2p@$PWD/packages/libp2p'
37+
# - name: js-libp2p-example-circuit-relay
38+
# repo: https://github.com/libp2p/js-libp2p-example-circuit-relay.git
39+
# deps:
40+
# - '@libp2p/circuit-relay-v2@$PWD/packages/transport-circuit-relay-v2'
41+
# - '@libp2p/identify@$PWD/packages/protocol-identify'
42+
# - '@libp2p/websockets@$PWD/packages/transport-websockets'
43+
# - 'libp2p@$PWD/packages/libp2p'
44+
# - name: js-libp2p-example-connection-encryption
45+
# repo: https://github.com/libp2p/js-libp2p-example-connection-encryption.git
46+
# deps:
47+
# - '@libp2p/plaintext@$PWD/packages/connection-encrypter-plaintext'
48+
# - '@libp2p/tcp@$PWD/packages/transport-tcp'
49+
# - 'libp2p@$PWD/packages/libp2p'
50+
# steps:
51+
# - uses: actions/checkout@v4
52+
# - uses: actions/setup-node@v4
53+
# with:
54+
# node-version: lts/*
55+
# - uses: ipfs/aegir/actions/cache-node-modules@master
56+
# - run: npx xvfb-maybe aegir test-dependant ${{ matrix.example.repo }} --deps ${{ join(matrix.example.deps, ',') }}

.release-please-manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"packages/connection-encrypter-plaintext":"1.1.6","packages/connection-encrypter-tls":"1.1.5","packages/crypto":"4.1.9","packages/interface":"1.7.0","packages/interface-compliance-tests":"5.4.12","packages/interface-internal":"1.3.4","packages/kad-dht":"12.1.5","packages/keychain":"4.1.6","packages/libp2p":"1.9.3","packages/logger":"4.0.20","packages/metrics-devtools":"0.2.5","packages/metrics-prometheus":"3.1.5","packages/metrics-simple":"1.1.5","packages/multistream-select":"5.1.17","packages/peer-collections":"5.2.9","packages/peer-discovery-bootstrap":"10.1.5","packages/peer-discovery-mdns":"10.1.5","packages/peer-id":"4.2.4","packages/peer-id-factory":"4.2.4","packages/peer-record":"7.0.25","packages/peer-store":"10.1.5","packages/protocol-autonat":"1.1.5","packages/protocol-dcutr":"1.1.5","packages/protocol-echo":"1.1.5","packages/protocol-fetch":"1.1.5","packages/protocol-identify":"2.1.5","packages/protocol-perf":"3.1.5","packages/protocol-ping":"1.1.6","packages/pubsub":"9.0.26","packages/pubsub-floodsub":"9.1.5","packages/record":"4.0.4","packages/stream-multiplexer-mplex":"10.1.5","packages/transport-circuit-relay-v2":"1.1.5","packages/transport-tcp":"9.1.5","packages/transport-webrtc":"4.1.8","packages/transport-websockets":"8.2.0","packages/transport-webtransport":"4.1.8","packages/upnp-nat":"1.2.5","packages/utils":"5.4.9"}
1+
{"packages/connection-encrypter-plaintext":"1.1.6","packages/connection-encrypter-tls":"1.1.5","packages/crypto":"4.1.9","packages/interface":"1.7.0","packages/interface-compliance-tests":"5.4.12","packages/interface-internal":"1.3.4","packages/kad-dht":"12.1.5","packages/keychain":"4.1.6","packages/libp2p":"1.9.3","packages/logger":"4.0.20","packages/metrics-devtools":"0.2.5","packages/metrics-prometheus":"3.1.5","packages/metrics-simple":"1.1.5","packages/multistream-select":"5.1.17","packages/peer-collections":"5.2.9","packages/peer-discovery-bootstrap":"10.1.5","packages/peer-discovery-mdns":"10.1.5","packages/peer-id":"4.2.4","packages/peer-record":"7.0.25","packages/peer-store":"10.1.5","packages/protocol-autonat":"1.1.5","packages/protocol-dcutr":"1.1.5","packages/protocol-echo":"1.1.5","packages/protocol-fetch":"1.1.5","packages/protocol-identify":"2.1.5","packages/protocol-perf":"3.1.5","packages/protocol-ping":"1.1.6","packages/pubsub":"9.0.26","packages/pubsub-floodsub":"9.1.5","packages/record":"4.0.4","packages/stream-multiplexer-mplex":"10.1.5","packages/transport-circuit-relay-v2":"1.1.5","packages/transport-tcp":"9.1.5","packages/transport-webrtc":"4.1.8","packages/transport-websockets":"8.2.0","packages/transport-webtransport":"4.1.8","packages/upnp-nat":"1.2.5","packages/utils":"5.4.9"}

.release-please.json

-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
"packages/peer-discovery-bootstrap": {},
2828
"packages/peer-discovery-mdns": {},
2929
"packages/peer-id": {},
30-
"packages/peer-id-factory": {},
3130
"packages/peer-record": {},
3231
"packages/peer-store": {},
3332
"packages/protocol-autonat": {},

doc/LIMITS.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,12 @@ When choosing connections to close the connection manager sorts the list of conn
5353

5454
```TypeScript
5555
import { createLibp2p } from 'libp2p'
56-
import { createEd25519PeerId } from '@libp2p/peer-id-factory'
56+
import { peerIdFromString } from '@libp2p/peer-id'
5757

5858

5959
const libp2p = await createLibp2p({})
6060

61-
const peerId = await createEd25519PeerId()
61+
const peerId = await peerIdFromString('123Koo...')
6262

6363
// tag a peer
6464
await libp2p.peerStore.merge(peerId, {

interop/BrowserDockerfile

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ WORKDIR /app
55
COPY package.json ./
66
COPY ./packages ./packages
77
COPY ./interop ./interop
8+
COPY ./patches ./patches
89

910
# disable colored output and CLI animation from test runners
1011
ENV CI=true

interop/Dockerfile

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ WORKDIR /app
77
COPY package.json ./
88
COPY ./packages ./packages
99
COPY ./interop ./interop
10+
COPY ./patches ./patches
1011

1112
# disable colored output and CLI animation from test runners
1213
ENV CI=true

interop/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"@chainsafe/libp2p-noise": "^15.0.0",
2222
"@chainsafe/libp2p-yamux": "^6.0.2",
2323
"@libp2p/circuit-relay-v2": "^1.0.24",
24-
"@libp2p/interface": "^1.4.0",
24+
"@libp2p/interface": "^1.7.0",
2525
"@libp2p/identify": "^2.0.2",
2626
"@libp2p/mplex": "^10.0.24",
2727
"@libp2p/ping": "^1.0.19",

package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@
3333
"npm:release": "aegir exec --bail false npm -- publish",
3434
"release:rc": "aegir release-rc",
3535
"docs": "aegir docs",
36-
"docs:no-publish": "aegir docs --publish false -- --exclude interop --exclude doc"
36+
"docs:no-publish": "aegir docs --publish false -- --exclude interop --exclude doc",
37+
"postinstall": "patch-package"
3738
},
3839
"devDependencies": {
3940
"aegir": "^44.0.1",
40-
"npm-run-all": "^4.1.5"
41+
"npm-run-all": "^4.1.5",
42+
"patch-package": "^8.0.0"
4143
},
4244
"eslintConfig": {
4345
"extends": "ipfs",

packages/connection-encrypter-plaintext/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@
5454
"dependencies": {
5555
"@libp2p/crypto": "^4.1.9",
5656
"@libp2p/interface": "^1.7.0",
57-
"@libp2p/peer-id-factory": "^4.2.4",
5857
"@libp2p/peer-id": "^4.2.4",
5958
"it-protobuf-stream": "^1.1.3",
6059
"it-stream-types": "^2.0.1",
@@ -63,6 +62,7 @@
6362
"uint8arrays": "^5.1.0"
6463
},
6564
"devDependencies": {
65+
"@libp2p/crypto": "^4.1.9",
6666
"@libp2p/interface-compliance-tests": "^5.4.12",
6767
"@libp2p/logger": "^4.0.20",
6868
"@multiformats/multiaddr": "^12.2.3",

packages/connection-encrypter-plaintext/src/index.ts

+26-43
Original file line numberDiff line numberDiff line change
@@ -20,30 +20,30 @@
2020
* ```
2121
*/
2222

23-
import { supportedKeys } from '@libp2p/crypto/keys'
24-
import { UnexpectedPeerError, InvalidCryptoExchangeError, serviceCapabilities } from '@libp2p/interface'
25-
import { peerIdFromBytes } from '@libp2p/peer-id'
26-
import { createFromPubKey } from '@libp2p/peer-id-factory'
23+
import { publicKeyFromRaw } from '@libp2p/crypto/keys'
24+
import { UnexpectedPeerError, InvalidCryptoExchangeError, serviceCapabilities, ProtocolError } from '@libp2p/interface'
25+
import { peerIdFromPublicKey } from '@libp2p/peer-id'
2726
import { pbStream } from 'it-protobuf-stream'
28-
import { Exchange, KeyType, PublicKey } from './pb/proto.js'
29-
import type { ComponentLogger, Logger, MultiaddrConnection, ConnectionEncrypter, SecuredConnection, PeerId, PublicKey as PubKey, SecureConnectionOptions } from '@libp2p/interface'
27+
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
28+
import { Exchange, KeyType } from './pb/proto.js'
29+
import type { ComponentLogger, Logger, MultiaddrConnection, ConnectionEncrypter, SecuredConnection, PrivateKey, SecureConnectionOptions } from '@libp2p/interface'
3030
import type { Duplex } from 'it-stream-types'
3131
import type { Uint8ArrayList } from 'uint8arraylist'
3232

3333
const PROTOCOL = '/plaintext/2.0.0'
3434

3535
export interface PlaintextComponents {
36-
peerId: PeerId
36+
privateKey: PrivateKey
3737
logger: ComponentLogger
3838
}
3939

4040
class Plaintext implements ConnectionEncrypter {
4141
public protocol: string = PROTOCOL
42-
private readonly peerId: PeerId
42+
private readonly privateKey: PrivateKey
4343
private readonly log: Logger
4444

4545
constructor (components: PlaintextComponents) {
46-
this.peerId = components.peerId
46+
this.privateKey = components.privateKey
4747
this.log = components.logger.forComponent('libp2p:plaintext')
4848
}
4949

@@ -54,38 +54,32 @@ class Plaintext implements ConnectionEncrypter {
5454
]
5555

5656
async secureInbound<Stream extends Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>> = MultiaddrConnection>(conn: Stream, options?: SecureConnectionOptions): Promise<SecuredConnection<Stream>> {
57-
return this._encrypt(this.peerId, conn, options)
57+
return this._encrypt(conn, options)
5858
}
5959

6060
async secureOutbound<Stream extends Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>> = MultiaddrConnection>(conn: Stream, options?: SecureConnectionOptions): Promise<SecuredConnection<Stream>> {
61-
return this._encrypt(this.peerId, conn, options)
61+
return this._encrypt(conn, options)
6262
}
6363

6464
/**
6565
* Encrypt connection
6666
*/
67-
async _encrypt<Stream extends Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>> = MultiaddrConnection>(localId: PeerId, conn: Stream, options?: SecureConnectionOptions): Promise<SecuredConnection<Stream>> {
67+
async _encrypt<Stream extends Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>> = MultiaddrConnection>(conn: Stream, options?: SecureConnectionOptions): Promise<SecuredConnection<Stream>> {
6868
const pb = pbStream(conn).pb(Exchange)
6969

70-
let type = KeyType.RSA
71-
72-
if (localId.type === 'Ed25519') {
73-
type = KeyType.Ed25519
74-
} else if (localId.type === 'secp256k1') {
75-
type = KeyType.Secp256k1
76-
}
77-
7870
this.log('write pubkey exchange to peer %p', options?.remotePeer)
7971

72+
const publicKey = this.privateKey.publicKey
73+
8074
const [
8175
, response
8276
] = await Promise.all([
8377
// Encode the public key and write it to the remote peer
8478
pb.write({
85-
id: localId.toBytes(),
79+
id: publicKey.toMultihash().bytes,
8680
pubkey: {
87-
Type: type,
88-
Data: localId.publicKey == null ? new Uint8Array(0) : (PublicKey.decode(localId.publicKey).Data ?? new Uint8Array(0))
81+
Type: KeyType[publicKey.type],
82+
Data: publicKey.raw
8983
}
9084
}, options),
9185
// Get the Exchange message
@@ -95,37 +89,26 @@ class Plaintext implements ConnectionEncrypter {
9589
let peerId
9690
try {
9791
if (response.pubkey == null) {
98-
throw new Error('Public key missing')
92+
throw new ProtocolError('Public key missing')
9993
}
10094

101-
if (response.pubkey.Data.length === 0) {
102-
throw new Error('Public key data too short')
95+
if (response.pubkey.Data.byteLength === 0) {
96+
throw new ProtocolError('Public key data too short')
10397
}
10498

10599
if (response.id == null) {
106-
throw new Error('Remote id missing')
107-
}
108-
109-
let pubKey: PubKey
110-
111-
if (response.pubkey.Type === KeyType.RSA) {
112-
pubKey = supportedKeys.rsa.unmarshalRsaPublicKey(response.pubkey.Data)
113-
} else if (response.pubkey.Type === KeyType.Ed25519) {
114-
pubKey = supportedKeys.ed25519.unmarshalEd25519PublicKey(response.pubkey.Data)
115-
} else if (response.pubkey.Type === KeyType.Secp256k1) {
116-
pubKey = supportedKeys.secp256k1.unmarshalSecp256k1PublicKey(response.pubkey.Data)
117-
} else {
118-
throw new Error('Unknown public key type')
100+
throw new ProtocolError('Remote id missing')
119101
}
120102

121-
peerId = await createFromPubKey(pubKey)
103+
const pubKey = publicKeyFromRaw(response.pubkey.Data)
104+
peerId = peerIdFromPublicKey(pubKey)
122105

123-
if (!peerId.equals(peerIdFromBytes(response.id))) {
124-
throw new Error('Public key did not match id')
106+
if (!uint8ArrayEquals(peerId.toMultihash().bytes, response.id)) {
107+
throw new InvalidCryptoExchangeError('Public key did not match id')
125108
}
126109
} catch (err: any) {
127110
this.log.error(err)
128-
throw new InvalidCryptoExchangeError('Remote did not provide its public key')
111+
throw new InvalidCryptoExchangeError('Invalid public key - ' + err.message)
129112
}
130113

131114
if (options?.remotePeer != null && !peerId.equals(options?.remotePeer)) {

packages/connection-encrypter-plaintext/src/pb/proto.proto

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ message Exchange {
88
enum KeyType {
99
RSA = 0;
1010
Ed25519 = 1;
11-
Secp256k1 = 2;
11+
secp256k1 = 2;
1212
ECDSA = 3;
1313
}
1414

packages/connection-encrypter-plaintext/src/pb/proto.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,14 @@ export namespace Exchange {
8181
export enum KeyType {
8282
RSA = 'RSA',
8383
Ed25519 = 'Ed25519',
84-
Secp256k1 = 'Secp256k1',
84+
secp256k1 = 'secp256k1',
8585
ECDSA = 'ECDSA'
8686
}
8787

8888
enum __KeyTypeValues {
8989
RSA = 0,
9090
Ed25519 = 1,
91-
Secp256k1 = 2,
91+
secp256k1 = 2,
9292
ECDSA = 3
9393
}
9494

packages/connection-encrypter-plaintext/test/compliance.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
/* eslint-env mocha */
22

3+
import { generateKeyPair } from '@libp2p/crypto/keys'
34
import suite from '@libp2p/interface-compliance-tests/connection-encryption'
45
import { defaultLogger } from '@libp2p/logger'
5-
import { createEd25519PeerId } from '@libp2p/peer-id-factory'
66
import { plaintext } from '../src/index.js'
77

88
describe('plaintext compliance', () => {
99
suite({
1010
async setup (opts) {
1111
return plaintext()({
12-
peerId: opts?.peerId ?? await createEd25519PeerId(),
12+
privateKey: opts?.privateKey ?? await generateKeyPair('Ed25519'),
1313
logger: defaultLogger()
1414
})
1515
},

packages/connection-encrypter-plaintext/test/index.spec.ts

+15-12
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
/* eslint-env mocha */
22

3+
import { generateKeyPair } from '@libp2p/crypto/keys'
34
import { mockMultiaddrConnPair } from '@libp2p/interface-compliance-tests/mocks'
45
import { defaultLogger } from '@libp2p/logger'
5-
import { peerIdFromBytes } from '@libp2p/peer-id'
6-
import { createEd25519PeerId, createRSAPeerId } from '@libp2p/peer-id-factory'
6+
import { peerIdFromMultihash, peerIdFromPrivateKey } from '@libp2p/peer-id'
77
import { multiaddr } from '@multiformats/multiaddr'
88
import { expect } from 'aegir/chai'
99
import sinon from 'sinon'
@@ -18,18 +18,20 @@ describe('plaintext', () => {
1818
let encrypterRemote: ConnectionEncrypter
1919

2020
beforeEach(async () => {
21-
[localPeer, remotePeer, wrongPeer] = await Promise.all([
22-
createEd25519PeerId(),
23-
createEd25519PeerId(),
24-
createEd25519PeerId()
21+
[remotePeer, wrongPeer] = await Promise.all([
22+
peerIdFromPrivateKey(await generateKeyPair('Ed25519')),
23+
peerIdFromPrivateKey(await generateKeyPair('Ed25519'))
2524
])
2625

26+
const localKeyPair = await generateKeyPair('Ed25519')
27+
localPeer = peerIdFromPrivateKey(localKeyPair)
28+
2729
encrypter = plaintext()({
28-
peerId: localPeer,
30+
privateKey: localKeyPair,
2931
logger: defaultLogger()
3032
})
3133
encrypterRemote = plaintext()({
32-
peerId: remotePeer,
34+
privateKey: await generateKeyPair('Ed25519'),
3335
logger: defaultLogger()
3436
})
3537
})
@@ -59,11 +61,12 @@ describe('plaintext', () => {
5961
})
6062

6163
it('should fail if the peer does not provide its public key', async () => {
62-
const peer = await createRSAPeerId()
63-
remotePeer = peerIdFromBytes(peer.toBytes())
64+
const keyPair = await generateKeyPair('RSA', 512)
65+
const peer = peerIdFromPrivateKey(keyPair)
66+
remotePeer = peerIdFromMultihash(peer.toMultihash())
6467

6568
encrypter = plaintext()({
66-
peerId: remotePeer,
69+
privateKey: keyPair,
6770
logger: defaultLogger()
6871
})
6972

@@ -81,6 +84,6 @@ describe('plaintext', () => {
8184
remotePeer: localPeer
8285
})
8386
]))
84-
.to.eventually.be.rejected.with.property('name', 'InvalidCryptoExchangeError')
87+
.to.eventually.be.rejected.with.property('name', 'UnexpectedPeerError')
8588
})
8689
})

0 commit comments

Comments
 (0)