@libp2p/noise 12.0.1 → 16.1.4-8484de8a2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -0
- package/dist/index.min.js +16 -20
- package/dist/index.min.js.map +7 -0
- package/dist/src/constants.d.ts +1 -0
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +1 -0
- package/dist/src/constants.js.map +1 -1
- package/dist/src/crypto/index.browser.d.ts +2 -0
- package/dist/src/crypto/index.browser.d.ts.map +1 -0
- package/dist/src/crypto/index.browser.js +3 -0
- package/dist/src/crypto/index.browser.js.map +1 -0
- package/dist/src/crypto/index.d.ts +3 -0
- package/dist/src/crypto/index.d.ts.map +1 -0
- package/dist/src/crypto/index.js +188 -0
- package/dist/src/crypto/index.js.map +1 -0
- package/dist/src/crypto/js.d.ts.map +1 -1
- package/dist/src/crypto/js.js +18 -17
- package/dist/src/crypto/js.js.map +1 -1
- package/dist/src/crypto.d.ts +11 -10
- package/dist/src/crypto.d.ts.map +1 -1
- package/dist/src/crypto.js +10 -1
- package/dist/src/crypto.js.map +1 -1
- package/dist/src/encoder.d.ts +5 -10
- package/dist/src/encoder.d.ts.map +1 -1
- package/dist/src/encoder.js +10 -51
- package/dist/src/encoder.js.map +1 -1
- package/dist/src/errors.d.ts +6 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +9 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/index.d.ts +50 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +38 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/logger.d.ts +9 -10
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/logger.js +39 -28
- package/dist/src/logger.js.map +1 -1
- package/dist/src/metrics.d.ts +1 -1
- package/dist/src/metrics.d.ts.map +1 -1
- package/dist/src/noise.d.ts +33 -30
- package/dist/src/noise.d.ts.map +1 -1
- package/dist/src/noise.js +122 -78
- package/dist/src/noise.js.map +1 -1
- package/dist/src/nonce.d.ts +2 -3
- package/dist/src/nonce.d.ts.map +1 -1
- package/dist/src/nonce.js +3 -2
- package/dist/src/nonce.js.map +1 -1
- package/dist/src/performHandshake.d.ts +5 -0
- package/dist/src/performHandshake.d.ts.map +1 -0
- package/dist/src/performHandshake.js +69 -0
- package/dist/src/performHandshake.js.map +1 -0
- package/dist/src/proto/payload.d.ts +4 -3
- package/dist/src/proto/payload.d.ts.map +1 -1
- package/dist/src/proto/payload.js +48 -29
- package/dist/src/proto/payload.js.map +1 -1
- package/dist/src/protocol.d.ts +71 -0
- package/dist/src/protocol.d.ts.map +1 -0
- package/dist/src/protocol.js +257 -0
- package/dist/src/protocol.js.map +1 -0
- package/dist/src/types.d.ts +98 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils.d.ts +10 -19
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +159 -57
- package/dist/src/utils.js.map +1 -1
- package/package.json +43 -48
- package/src/constants.ts +1 -0
- package/src/crypto/index.browser.ts +3 -0
- package/src/crypto/index.ts +219 -0
- package/src/crypto/js.ts +25 -27
- package/src/crypto.ts +21 -10
- package/src/encoder.ts +10 -67
- package/src/errors.ts +10 -0
- package/src/index.ts +54 -6
- package/src/logger.ts +46 -32
- package/src/metrics.ts +1 -1
- package/src/noise.ts +176 -131
- package/src/nonce.ts +7 -7
- package/src/performHandshake.ts +91 -0
- package/src/proto/payload.proto +2 -1
- package/src/proto/payload.ts +53 -31
- package/src/protocol.ts +313 -0
- package/src/types.ts +105 -0
- package/src/utils.ts +189 -79
- package/dist/src/@types/basic.d.ts +0 -5
- package/dist/src/@types/basic.d.ts.map +0 -1
- package/dist/src/@types/basic.js +0 -2
- package/dist/src/@types/basic.js.map +0 -1
- package/dist/src/@types/handshake-interface.d.ts +0 -15
- package/dist/src/@types/handshake-interface.d.ts.map +0 -1
- package/dist/src/@types/handshake-interface.js +0 -2
- package/dist/src/@types/handshake-interface.js.map +0 -1
- package/dist/src/@types/handshake.d.ts +0 -40
- package/dist/src/@types/handshake.d.ts.map +0 -1
- package/dist/src/@types/handshake.js +0 -2
- package/dist/src/@types/handshake.js.map +0 -1
- package/dist/src/@types/libp2p.d.ts +0 -10
- package/dist/src/@types/libp2p.d.ts.map +0 -1
- package/dist/src/@types/libp2p.js +0 -2
- package/dist/src/@types/libp2p.js.map +0 -1
- package/dist/src/crypto/streaming.d.ts +0 -7
- package/dist/src/crypto/streaming.d.ts.map +0 -1
- package/dist/src/crypto/streaming.js +0 -50
- package/dist/src/crypto/streaming.js.map +0 -1
- package/dist/src/handshake-xx.d.ts +0 -33
- package/dist/src/handshake-xx.d.ts.map +0 -1
- package/dist/src/handshake-xx.js +0 -139
- package/dist/src/handshake-xx.js.map +0 -1
- package/dist/src/handshakes/abstract-handshake.d.ts +0 -36
- package/dist/src/handshakes/abstract-handshake.d.ts.map +0 -1
- package/dist/src/handshakes/abstract-handshake.js +0 -140
- package/dist/src/handshakes/abstract-handshake.js.map +0 -1
- package/dist/src/handshakes/xx.d.ts +0 -18
- package/dist/src/handshakes/xx.d.ts.map +0 -1
- package/dist/src/handshakes/xx.js +0 -161
- package/dist/src/handshakes/xx.js.map +0 -1
- package/src/@types/basic.ts +0 -5
- package/src/@types/handshake-interface.ts +0 -12
- package/src/@types/handshake.ts +0 -48
- package/src/@types/libp2p.ts +0 -10
- package/src/crypto/streaming.ts +0 -58
- package/src/handshake-xx.ts +0 -177
- package/src/handshakes/abstract-handshake.ts +0 -180
- package/src/handshakes/xx.ts +0 -184
package/src/noise.ts
CHANGED
|
@@ -1,192 +1,237 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { publicKeyFromProtobuf } from '@libp2p/crypto/keys'
|
|
2
|
+
import { InvalidCryptoExchangeError, serviceCapabilities } from '@libp2p/interface'
|
|
3
|
+
import { peerIdFromPublicKey } from '@libp2p/peer-id'
|
|
4
|
+
import { lpStream } from '@libp2p/utils'
|
|
5
|
+
import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc'
|
|
5
6
|
import { NOISE_MSG_MAX_LENGTH_BYTES } from './constants.js'
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
7
|
+
import { defaultCrypto } from './crypto/index.js'
|
|
8
|
+
import { wrapCrypto } from './crypto.js'
|
|
8
9
|
import { uint16BEDecode, uint16BEEncode } from './encoder.js'
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import type { bytes } from './@types/basic.js'
|
|
13
|
-
import type { IHandshake } from './@types/handshake-interface.js'
|
|
14
|
-
import type { INoiseConnection, KeyPair } from './@types/libp2p.js'
|
|
10
|
+
import { registerMetrics } from './metrics.js'
|
|
11
|
+
import { performHandshakeInitiator, performHandshakeResponder } from './performHandshake.js'
|
|
12
|
+
import { toMessageStream } from './utils.ts'
|
|
15
13
|
import type { ICryptoInterface } from './crypto.js'
|
|
16
|
-
import type {
|
|
17
|
-
import type {
|
|
18
|
-
import type {
|
|
19
|
-
import type {
|
|
20
|
-
import type {
|
|
21
|
-
|
|
22
|
-
interface
|
|
23
|
-
|
|
24
|
-
isInitiator: boolean
|
|
25
|
-
localPeer: PeerId
|
|
26
|
-
remotePeer?: PeerId
|
|
14
|
+
import type { NoiseComponents } from './index.js'
|
|
15
|
+
import type { MetricsRegistry } from './metrics.js'
|
|
16
|
+
import type { HandshakeResult, ICrypto, INoiseConnection, INoiseExtensions, KeyPair } from './types.js'
|
|
17
|
+
import type { SecuredConnection, PrivateKey, PublicKey, StreamMuxerFactory, SecureConnectionOptions, Logger, MessageStream } from '@libp2p/interface'
|
|
18
|
+
import type { LengthPrefixedStream } from '@libp2p/utils'
|
|
19
|
+
|
|
20
|
+
export interface NoiseExtensions {
|
|
21
|
+
webtransportCerthashes: Uint8Array[]
|
|
27
22
|
}
|
|
28
23
|
|
|
29
24
|
export interface NoiseInit {
|
|
30
25
|
/**
|
|
31
26
|
* x25519 private key, reuse for faster handshakes
|
|
32
27
|
*/
|
|
33
|
-
staticNoiseKey?:
|
|
34
|
-
extensions?: NoiseExtensions
|
|
28
|
+
staticNoiseKey?: Uint8Array
|
|
29
|
+
extensions?: Partial<NoiseExtensions>
|
|
35
30
|
crypto?: ICryptoInterface
|
|
36
31
|
prologueBytes?: Uint8Array
|
|
37
|
-
metrics?: Metrics
|
|
38
32
|
}
|
|
39
33
|
|
|
40
34
|
export class Noise implements INoiseConnection {
|
|
41
35
|
public protocol = '/noise'
|
|
42
|
-
public crypto:
|
|
36
|
+
public crypto: ICrypto
|
|
43
37
|
|
|
44
38
|
private readonly prologue: Uint8Array
|
|
45
|
-
private readonly
|
|
39
|
+
private readonly staticKey: KeyPair
|
|
46
40
|
private readonly extensions?: NoiseExtensions
|
|
47
41
|
private readonly metrics?: MetricsRegistry
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
42
|
+
private readonly components: NoiseComponents
|
|
43
|
+
private readonly log: Logger
|
|
44
|
+
|
|
45
|
+
constructor (components: NoiseComponents, init: NoiseInit = {}) {
|
|
46
|
+
const { staticNoiseKey, extensions, crypto, prologueBytes } = init
|
|
47
|
+
const { metrics } = components
|
|
48
|
+
|
|
49
|
+
this.components = components
|
|
50
|
+
this.log = components.logger.forComponent('libp2p:noise')
|
|
51
|
+
const _crypto = crypto ?? defaultCrypto
|
|
52
|
+
this.crypto = wrapCrypto(_crypto)
|
|
53
|
+
this.extensions = {
|
|
54
|
+
webtransportCerthashes: [],
|
|
55
|
+
...extensions
|
|
56
|
+
}
|
|
54
57
|
this.metrics = metrics ? registerMetrics(metrics) : undefined
|
|
55
58
|
|
|
56
59
|
if (staticNoiseKey) {
|
|
57
60
|
// accepts x25519 private key of length 32
|
|
58
|
-
this.
|
|
61
|
+
this.staticKey = _crypto.generateX25519KeyPairFromSeed(staticNoiseKey)
|
|
59
62
|
} else {
|
|
60
|
-
this.
|
|
63
|
+
this.staticKey = _crypto.generateX25519KeyPair()
|
|
61
64
|
}
|
|
62
|
-
this.prologue = prologueBytes ??
|
|
65
|
+
this.prologue = prologueBytes ?? uint8ArrayAlloc(0)
|
|
63
66
|
}
|
|
64
67
|
|
|
68
|
+
readonly [Symbol.toStringTag] = '@libp2p/noise'
|
|
69
|
+
|
|
70
|
+
readonly [serviceCapabilities]: string[] = [
|
|
71
|
+
'@libp2p/connection-encryption',
|
|
72
|
+
'@libp2p/noise'
|
|
73
|
+
]
|
|
74
|
+
|
|
65
75
|
/**
|
|
66
76
|
* Encrypt outgoing data to the remote party (handshake as initiator)
|
|
67
77
|
*
|
|
68
|
-
* @param
|
|
69
|
-
* @param
|
|
70
|
-
* @param
|
|
71
|
-
* @
|
|
78
|
+
* @param connection - streaming iterable duplex that will be encrypted
|
|
79
|
+
* @param options
|
|
80
|
+
* @param options.remotePeer - PeerId of the remote peer. Used to validate the integrity of the remote peer
|
|
81
|
+
* @param options.signal - Used to abort the operation
|
|
72
82
|
*/
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
|
|
80
|
-
}
|
|
81
|
-
)
|
|
82
|
-
const handshake = await this.performHandshake({
|
|
83
|
-
connection: wrappedConnection,
|
|
84
|
-
isInitiator: true,
|
|
85
|
-
localPeer,
|
|
86
|
-
remotePeer
|
|
83
|
+
async secureOutbound (connection: MessageStream, options?: SecureConnectionOptions): Promise<SecuredConnection<INoiseExtensions>> {
|
|
84
|
+
const log = connection.log?.newScope('noise') ?? this.log
|
|
85
|
+
const wrappedConnection = lpStream(connection, {
|
|
86
|
+
lengthEncoder: uint16BEEncode,
|
|
87
|
+
lengthDecoder: uint16BEDecode,
|
|
88
|
+
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
|
|
87
89
|
})
|
|
88
|
-
|
|
90
|
+
|
|
91
|
+
const handshake = await this.performHandshakeInitiator(
|
|
92
|
+
wrappedConnection,
|
|
93
|
+
this.components.privateKey,
|
|
94
|
+
log,
|
|
95
|
+
options?.remotePeer?.publicKey,
|
|
96
|
+
options
|
|
97
|
+
)
|
|
98
|
+
const publicKey = publicKeyFromProtobuf(handshake.payload.identityKey)
|
|
89
99
|
|
|
90
100
|
return {
|
|
91
|
-
|
|
92
|
-
remoteExtensions: handshake.
|
|
93
|
-
remotePeer:
|
|
101
|
+
connection: toMessageStream(wrappedConnection.unwrap(), handshake, this.metrics),
|
|
102
|
+
remoteExtensions: handshake.payload.extensions,
|
|
103
|
+
remotePeer: peerIdFromPublicKey(publicKey),
|
|
104
|
+
streamMuxer: options?.skipStreamMuxerNegotiation === true ? undefined : this.getStreamMuxer(handshake.payload.extensions?.streamMuxers)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private getStreamMuxer (protocols?: string[]): StreamMuxerFactory | undefined {
|
|
109
|
+
if (protocols == null || protocols.length === 0) {
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const streamMuxers = this.components.upgrader.getStreamMuxers()
|
|
114
|
+
|
|
115
|
+
if (streamMuxers != null) {
|
|
116
|
+
for (const protocol of protocols) {
|
|
117
|
+
const streamMuxer = streamMuxers.get(protocol)
|
|
118
|
+
|
|
119
|
+
if (streamMuxer != null) {
|
|
120
|
+
return streamMuxer
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (protocols.length) {
|
|
126
|
+
throw new InvalidCryptoExchangeError('Early muxer negotiation was requested but the initiator and responder had no common muxers')
|
|
94
127
|
}
|
|
95
128
|
}
|
|
96
129
|
|
|
97
130
|
/**
|
|
98
131
|
* Decrypt incoming data (handshake as responder).
|
|
99
132
|
*
|
|
100
|
-
* @param
|
|
101
|
-
* @param
|
|
102
|
-
* @param
|
|
103
|
-
* @
|
|
133
|
+
* @param connection - streaming iterable duplex that will be encrypted
|
|
134
|
+
* @param options
|
|
135
|
+
* @param options.remotePeer - PeerId of the remote peer. Used to validate the integrity of the remote peer
|
|
136
|
+
* @param options.signal - Used to abort the operation
|
|
104
137
|
*/
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
|
|
112
|
-
}
|
|
113
|
-
)
|
|
114
|
-
const handshake = await this.performHandshake({
|
|
115
|
-
connection: wrappedConnection,
|
|
116
|
-
isInitiator: false,
|
|
117
|
-
localPeer,
|
|
118
|
-
remotePeer
|
|
138
|
+
async secureInbound (connection: MessageStream, options?: SecureConnectionOptions): Promise<SecuredConnection<INoiseExtensions>> {
|
|
139
|
+
const log = connection.log?.newScope('noise') ?? this.log
|
|
140
|
+
const wrappedConnection = lpStream(connection, {
|
|
141
|
+
lengthEncoder: uint16BEEncode,
|
|
142
|
+
lengthDecoder: uint16BEDecode,
|
|
143
|
+
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
|
|
119
144
|
})
|
|
120
|
-
|
|
145
|
+
|
|
146
|
+
const handshake = await this.performHandshakeResponder(
|
|
147
|
+
wrappedConnection,
|
|
148
|
+
this.components.privateKey,
|
|
149
|
+
log,
|
|
150
|
+
options?.remotePeer?.publicKey,
|
|
151
|
+
options
|
|
152
|
+
)
|
|
153
|
+
const publicKey = publicKeyFromProtobuf(handshake.payload.identityKey)
|
|
121
154
|
|
|
122
155
|
return {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
156
|
+
connection: toMessageStream(wrappedConnection.unwrap(), handshake, this.metrics),
|
|
157
|
+
remoteExtensions: handshake.payload.extensions,
|
|
158
|
+
remotePeer: peerIdFromPublicKey(publicKey),
|
|
159
|
+
streamMuxer: options?.skipStreamMuxerNegotiation === true ? undefined : this.getStreamMuxer(handshake.payload.extensions?.streamMuxers)
|
|
126
160
|
}
|
|
127
161
|
}
|
|
128
162
|
|
|
129
163
|
/**
|
|
130
|
-
*
|
|
131
|
-
* If noise pipes disabled or remote peer static key is unknown, use XX.
|
|
132
|
-
*
|
|
133
|
-
* @param {HandshakeParams} params
|
|
164
|
+
* Perform XX handshake as initiator.
|
|
134
165
|
*/
|
|
135
|
-
private async
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
): Promise<XXHandshake> {
|
|
146
|
-
const { isInitiator, remotePeer, connection } = params
|
|
147
|
-
const handshake = new XXHandshake(
|
|
148
|
-
isInitiator,
|
|
149
|
-
payload,
|
|
150
|
-
this.prologue,
|
|
151
|
-
this.crypto,
|
|
152
|
-
this.staticKeys,
|
|
153
|
-
connection,
|
|
154
|
-
remotePeer
|
|
155
|
-
)
|
|
166
|
+
private async performHandshakeInitiator (
|
|
167
|
+
connection: LengthPrefixedStream,
|
|
168
|
+
// TODO: pass private key in noise constructor via Components
|
|
169
|
+
privateKey: PrivateKey,
|
|
170
|
+
log: Logger,
|
|
171
|
+
remoteIdentityKey?: PublicKey,
|
|
172
|
+
options?: SecureConnectionOptions
|
|
173
|
+
): Promise<HandshakeResult> {
|
|
174
|
+
let result: HandshakeResult
|
|
175
|
+
const streamMuxers = options?.skipStreamMuxerNegotiation === true ? [] : [...this.components.upgrader.getStreamMuxers().keys()]
|
|
156
176
|
|
|
157
177
|
try {
|
|
158
|
-
await
|
|
159
|
-
|
|
160
|
-
|
|
178
|
+
result = await performHandshakeInitiator({
|
|
179
|
+
connection,
|
|
180
|
+
privateKey,
|
|
181
|
+
remoteIdentityKey,
|
|
182
|
+
log: log.newScope('xxhandshake'),
|
|
183
|
+
crypto: this.crypto,
|
|
184
|
+
prologue: this.prologue,
|
|
185
|
+
s: this.staticKey,
|
|
186
|
+
extensions: {
|
|
187
|
+
streamMuxers,
|
|
188
|
+
webtransportCerthashes: [],
|
|
189
|
+
...this.extensions
|
|
190
|
+
}
|
|
191
|
+
}, options)
|
|
161
192
|
this.metrics?.xxHandshakeSuccesses.increment()
|
|
162
193
|
} catch (e: unknown) {
|
|
163
194
|
this.metrics?.xxHandshakeErrors.increment()
|
|
164
|
-
|
|
165
|
-
e.message = `Error occurred during XX handshake: ${e.message}`
|
|
166
|
-
throw e
|
|
167
|
-
}
|
|
195
|
+
throw e
|
|
168
196
|
}
|
|
169
197
|
|
|
170
|
-
return
|
|
198
|
+
return result
|
|
171
199
|
}
|
|
172
200
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
201
|
+
/**
|
|
202
|
+
* Perform XX handshake as responder.
|
|
203
|
+
*/
|
|
204
|
+
private async performHandshakeResponder (
|
|
205
|
+
connection: LengthPrefixedStream,
|
|
206
|
+
privateKey: PrivateKey,
|
|
207
|
+
log: Logger,
|
|
208
|
+
remoteIdentityKey?: PublicKey,
|
|
209
|
+
options?: SecureConnectionOptions
|
|
210
|
+
): Promise<HandshakeResult> {
|
|
211
|
+
let result: HandshakeResult
|
|
212
|
+
const streamMuxers = options?.skipStreamMuxerNegotiation === true ? [] : [...this.components.upgrader.getStreamMuxers().keys()]
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
result = await performHandshakeResponder({
|
|
216
|
+
connection,
|
|
217
|
+
privateKey,
|
|
218
|
+
remoteIdentityKey,
|
|
219
|
+
log: log.newScope('xxhandshake'),
|
|
220
|
+
crypto: this.crypto,
|
|
221
|
+
prologue: this.prologue,
|
|
222
|
+
s: this.staticKey,
|
|
223
|
+
extensions: {
|
|
224
|
+
streamMuxers,
|
|
225
|
+
webtransportCerthashes: [],
|
|
226
|
+
...this.extensions
|
|
227
|
+
}
|
|
228
|
+
}, options)
|
|
229
|
+
this.metrics?.xxHandshakeSuccesses.increment()
|
|
230
|
+
} catch (e: unknown) {
|
|
231
|
+
this.metrics?.xxHandshakeErrors.increment()
|
|
232
|
+
throw e
|
|
233
|
+
}
|
|
189
234
|
|
|
190
|
-
return
|
|
235
|
+
return result
|
|
191
236
|
}
|
|
192
237
|
}
|
package/src/nonce.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc'
|
|
2
2
|
|
|
3
3
|
export const MIN_NONCE = 0
|
|
4
4
|
// For performance reasons, the nonce is represented as a JS `number`
|
|
@@ -9,20 +9,20 @@ export const MIN_NONCE = 0
|
|
|
9
9
|
// this MAX_NONCE is still a large number of messages, so the practical effect of this is negligible.
|
|
10
10
|
export const MAX_NONCE = 0xffffffff
|
|
11
11
|
|
|
12
|
-
const ERR_MAX_NONCE = '
|
|
12
|
+
const ERR_MAX_NONCE = 'Cipher state has reached maximum n, a new handshake must be performed'
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* The nonce is an uint that's increased over time.
|
|
16
16
|
* Maintaining different representations help improve performance.
|
|
17
17
|
*/
|
|
18
18
|
export class Nonce {
|
|
19
|
-
private n:
|
|
20
|
-
private readonly bytes:
|
|
19
|
+
private n: number
|
|
20
|
+
private readonly bytes: Uint8Array
|
|
21
21
|
private readonly view: DataView
|
|
22
22
|
|
|
23
23
|
constructor (n = MIN_NONCE) {
|
|
24
24
|
this.n = n
|
|
25
|
-
this.bytes =
|
|
25
|
+
this.bytes = uint8ArrayAlloc(12)
|
|
26
26
|
this.view = new DataView(this.bytes.buffer, this.bytes.byteOffset, this.bytes.byteLength)
|
|
27
27
|
this.view.setUint32(4, n, true)
|
|
28
28
|
}
|
|
@@ -33,11 +33,11 @@ export class Nonce {
|
|
|
33
33
|
this.view.setUint32(4, this.n, true)
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
getBytes ():
|
|
36
|
+
getBytes (): Uint8Array {
|
|
37
37
|
return this.bytes
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
getUint64 ():
|
|
40
|
+
getUint64 (): number {
|
|
41
41
|
return this.n
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {
|
|
2
|
+
logLocalStaticKeys,
|
|
3
|
+
logLocalEphemeralKeys,
|
|
4
|
+
logRemoteEphemeralKey,
|
|
5
|
+
logRemoteStaticKey,
|
|
6
|
+
logCipherState
|
|
7
|
+
} from './logger.js'
|
|
8
|
+
import { ZEROLEN, XXHandshakeState } from './protocol.js'
|
|
9
|
+
import { createHandshakePayload, decodeHandshakePayload } from './utils.js'
|
|
10
|
+
import type { HandshakeResult, HandshakeParams } from './types.js'
|
|
11
|
+
import type { AbortOptions } from '@libp2p/interface'
|
|
12
|
+
|
|
13
|
+
export async function performHandshakeInitiator (init: HandshakeParams, options?: AbortOptions): Promise<HandshakeResult> {
|
|
14
|
+
const { log, connection, crypto, privateKey, prologue, s, remoteIdentityKey, extensions } = init
|
|
15
|
+
|
|
16
|
+
const payload = await createHandshakePayload(privateKey, s.publicKey, extensions)
|
|
17
|
+
const xx = new XXHandshakeState({
|
|
18
|
+
crypto,
|
|
19
|
+
protocolName: 'Noise_XX_25519_ChaChaPoly_SHA256',
|
|
20
|
+
initiator: true,
|
|
21
|
+
prologue,
|
|
22
|
+
s
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
logLocalStaticKeys(xx.s, log)
|
|
26
|
+
log.trace('Stage 0 - Initiator starting to send first message.')
|
|
27
|
+
await connection.write(xx.writeMessageA(ZEROLEN), options)
|
|
28
|
+
log.trace('Stage 0 - Initiator finished sending first message.')
|
|
29
|
+
logLocalEphemeralKeys(xx.e, log)
|
|
30
|
+
|
|
31
|
+
log.trace('Stage 1 - Initiator waiting to receive first message from responder...')
|
|
32
|
+
const plaintext = xx.readMessageB(await connection.read(options))
|
|
33
|
+
log.trace('Stage 1 - Initiator received the message.')
|
|
34
|
+
logRemoteEphemeralKey(xx.re, log)
|
|
35
|
+
logRemoteStaticKey(xx.rs, log)
|
|
36
|
+
|
|
37
|
+
log.trace("Initiator going to check remote's signature...")
|
|
38
|
+
const receivedPayload = await decodeHandshakePayload(plaintext, xx.rs, remoteIdentityKey)
|
|
39
|
+
log.trace('All good with the signature!')
|
|
40
|
+
|
|
41
|
+
log.trace('Stage 2 - Initiator sending third handshake message.')
|
|
42
|
+
await connection.write(xx.writeMessageC(payload), options)
|
|
43
|
+
log.trace('Stage 2 - Initiator sent message with signed payload.')
|
|
44
|
+
|
|
45
|
+
const [cs1, cs2] = xx.ss.split()
|
|
46
|
+
logCipherState(cs1, cs2, log)
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
payload: receivedPayload,
|
|
50
|
+
encrypt: (plaintext) => cs1.encryptWithAd(ZEROLEN, plaintext),
|
|
51
|
+
decrypt: (ciphertext, dst) => cs2.decryptWithAd(ZEROLEN, ciphertext, dst)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function performHandshakeResponder (init: HandshakeParams, options?: AbortOptions): Promise<HandshakeResult> {
|
|
56
|
+
const { log, connection, crypto, privateKey, prologue, s, remoteIdentityKey, extensions } = init
|
|
57
|
+
|
|
58
|
+
const payload = await createHandshakePayload(privateKey, s.publicKey, extensions)
|
|
59
|
+
const xx = new XXHandshakeState({
|
|
60
|
+
crypto,
|
|
61
|
+
protocolName: 'Noise_XX_25519_ChaChaPoly_SHA256',
|
|
62
|
+
initiator: false,
|
|
63
|
+
prologue,
|
|
64
|
+
s
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
logLocalStaticKeys(xx.s, log)
|
|
68
|
+
log.trace('Stage 0 - Responder waiting to receive first message.')
|
|
69
|
+
xx.readMessageA(await connection.read(options))
|
|
70
|
+
log.trace('Stage 0 - Responder received first message.')
|
|
71
|
+
logRemoteEphemeralKey(xx.re, log)
|
|
72
|
+
|
|
73
|
+
log.trace('Stage 1 - Responder sending out first message with signed payload and static key.')
|
|
74
|
+
await connection.write(xx.writeMessageB(payload), options)
|
|
75
|
+
log.trace('Stage 1 - Responder sent the second handshake message with signed payload.')
|
|
76
|
+
logLocalEphemeralKeys(xx.e, log)
|
|
77
|
+
|
|
78
|
+
log.trace('Stage 2 - Responder waiting for third handshake message...')
|
|
79
|
+
const plaintext = xx.readMessageC(await connection.read(options))
|
|
80
|
+
log.trace('Stage 2 - Responder received the message, finished handshake.')
|
|
81
|
+
const receivedPayload = await decodeHandshakePayload(plaintext, xx.rs, remoteIdentityKey)
|
|
82
|
+
|
|
83
|
+
const [cs1, cs2] = xx.ss.split()
|
|
84
|
+
logCipherState(cs1, cs2, log)
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
payload: receivedPayload,
|
|
88
|
+
encrypt: (plaintext) => cs2.encryptWithAd(ZEROLEN, plaintext),
|
|
89
|
+
decrypt: (ciphertext, dst) => cs1.decryptWithAd(ZEROLEN, ciphertext, dst)
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/proto/payload.proto
CHANGED
|
@@ -2,10 +2,11 @@ syntax = "proto3";
|
|
|
2
2
|
|
|
3
3
|
message NoiseExtensions {
|
|
4
4
|
repeated bytes webtransport_certhashes = 1;
|
|
5
|
+
repeated string stream_muxers = 2;
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
message NoiseHandshakePayload {
|
|
8
9
|
bytes identity_key = 1;
|
|
9
10
|
bytes identity_sig = 2;
|
|
10
11
|
optional NoiseExtensions extensions = 4;
|
|
11
|
-
}
|
|
12
|
+
}
|