Skip to content

Commit ad25a5e

Browse files
fix!: yield uint8arraylists instead of concatenating buffers (#391)
* fix: yield uint8arraylists instead of concatenating buffers In order to avoid unnecessary buffer copies, update to the new libp2p connection encrypter API that lets connection encrypters consume/yield lists of buffers instead of requiring them to be concatenated before/after encryption/decryption. * chore: fix tcp version * feat: use libp2p component logger Refactors code to use the component logger from libp2p to allow more flexible logging patterns. Nb. adds a `NoiseComponents` interface separate from `NoiseInit` that contains the `Metrics` instance - this is consistent with every other libp2p module. Refs: https://github.com/libp2p/js-libp2p/issue/2105 Refs: libp2p/js-libp2p#2198 Refs: https://github.com/libp2p/js-libp2p/issue/378 * chore: fix linter errors --------- Co-authored-by: Cayman <[email protected]>
1 parent ea9f556 commit ad25a5e

25 files changed

+353
-217
lines changed

benchmarks/benchmark.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ const bench = async function () {
2222
console.log('Init complete, running benchmark')
2323
const bench = new benchmark('handshake', {
2424
defer: true,
25-
fn: async function (deffered) {
25+
fn: async function (deferred) {
2626
const [inboundConnection, outboundConnection] = duplexPair()
2727
await Promise.all([
2828
initiator.secureOutbound(initiatorPeer, outboundConnection, responderPeer),
2929
responder.secureInbound(responderPeer, inboundConnection, initiatorPeer)
3030
])
31-
deffered.resolve()
31+
deferred.resolve()
3232
}
3333
})
3434
.on('complete', function (stats) {

package.json

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,9 @@
6969
"dependencies": {
7070
"@chainsafe/as-chacha20poly1305": "^0.1.0",
7171
"@chainsafe/as-sha256": "^0.4.1",
72-
"@libp2p/crypto": "^2.0.0",
73-
"@libp2p/interface": "^0.1.0",
74-
"@libp2p/logger": "^3.0.0",
75-
"@libp2p/peer-id": "^3.0.0",
72+
"@libp2p/crypto": "^3.0.0",
73+
"@libp2p/interface": "^1.0.0",
74+
"@libp2p/peer-id": "^4.0.0",
7675
"@noble/ciphers": "^0.4.0",
7776
"@noble/curves": "^1.1.0",
7877
"@noble/hashes": "^1.3.1",
@@ -88,22 +87,22 @@
8887
"wherearewe": "^2.0.1"
8988
},
9089
"devDependencies": {
91-
"@chainsafe/libp2p-yamux": "^5.0.0",
90+
"@chainsafe/libp2p-yamux": "^6.0.0",
9291
"@libp2p/daemon-client": "^7.0.0",
9392
"@libp2p/daemon-server": "^6.0.0",
94-
"@libp2p/interface-compliance-tests": "^4.0.0",
95-
"@libp2p/interface-peer-id": "^2.0.2",
93+
"@libp2p/interface-compliance-tests": "^5.0.0",
9694
"@libp2p/interop": "^9.0.0",
97-
"@libp2p/peer-id-factory": "^3.0.0",
98-
"@libp2p/tcp": "^8.0.0",
95+
"@libp2p/logger": "^4.0.0",
96+
"@libp2p/peer-id-factory": "^3.0.9",
97+
"@libp2p/tcp": "^9.0.0",
9998
"@multiformats/multiaddr": "^12.1.0",
10099
"@types/sinon": "^17.0.1",
101100
"aegir": "^41.1.10",
102101
"benchmark": "^2.1.4",
103102
"execa": "^8.0.1",
104103
"go-libp2p": "^1.0.3",
105104
"iso-random-stream": "^2.0.2",
106-
"libp2p": "^0.46.0",
105+
"libp2p": "next",
107106
"mkdirp": "^3.0.0",
108107
"p-defer": "^4.0.0",
109108
"protons": "^7.0.0",

src/@types/handshake-interface.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import type { bytes } from './basic.js'
21
import type { NoiseSession } from './handshake.js'
32
import type { NoiseExtensions } from '../proto/payload.js'
4-
import type { PeerId } from '@libp2p/interface/peer-id'
3+
import type { PeerId } from '@libp2p/interface'
4+
import type { Uint8ArrayList } from 'uint8arraylist'
55

66
export interface IHandshake {
77
session: NoiseSession
88
remotePeer: PeerId
99
remoteExtensions: NoiseExtensions
10-
encrypt(plaintext: bytes, session: NoiseSession): bytes
11-
decrypt(ciphertext: bytes, session: NoiseSession, dst?: Uint8Array): { plaintext: bytes, valid: boolean }
10+
encrypt(plaintext: Uint8Array | Uint8ArrayList, session: NoiseSession): Uint8Array | Uint8ArrayList
11+
decrypt(ciphertext: Uint8Array | Uint8ArrayList, session: NoiseSession, dst?: Uint8Array): { plaintext: Uint8Array | Uint8ArrayList, valid: boolean }
1212
}

src/@types/handshake.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import type { bytes, bytes32, uint64 } from './basic.js'
22
import type { KeyPair } from './libp2p.js'
33
import type { Nonce } from '../nonce.js'
4+
import type { Uint8ArrayList } from 'uint8arraylist'
45

56
export type Hkdf = [bytes, bytes, bytes]
67

78
export interface MessageBuffer {
89
ne: bytes32
9-
ns: bytes
10-
ciphertext: bytes
10+
ns: Uint8Array | Uint8ArrayList
11+
ciphertext: Uint8Array | Uint8ArrayList
1112
}
1213

1314
export interface CipherState {
@@ -27,7 +28,7 @@ export interface HandshakeState {
2728
ss: SymmetricState
2829
s: KeyPair
2930
e?: KeyPair
30-
rs: bytes32
31+
rs: Uint8Array | Uint8ArrayList
3132
re: bytes32
3233
psk: bytes32
3334
}

src/@types/libp2p.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { bytes32 } from './basic.js'
22
import type { NoiseExtensions } from '../proto/payload.js'
3-
import type { ConnectionEncrypter } from '@libp2p/interface/connection-encrypter'
3+
import type { ConnectionEncrypter } from '@libp2p/interface'
44

55
export interface KeyPair {
66
publicKey: bytes32

src/crypto.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
import type { bytes32, bytes } from './@types/basic.js'
1+
import { type Uint8ArrayList } from 'uint8arraylist'
2+
import type { bytes32 } from './@types/basic.js'
23
import type { Hkdf } from './@types/handshake.js'
34
import type { KeyPair } from './@types/libp2p.js'
45

56
export interface ICryptoInterface {
6-
hashSHA256(data: Uint8Array): Uint8Array
7+
hashSHA256(data: Uint8Array | Uint8ArrayList): Uint8Array
78

89
getHKDF(ck: bytes32, ikm: Uint8Array): Hkdf
910

1011
generateX25519KeyPair(): KeyPair
1112
generateX25519KeyPairFromSeed(seed: Uint8Array): KeyPair
12-
generateX25519SharedKey(privateKey: Uint8Array, publicKey: Uint8Array): Uint8Array
13+
generateX25519SharedKey(privateKey: Uint8Array | Uint8ArrayList, publicKey: Uint8Array | Uint8ArrayList): Uint8Array
1314

14-
chaCha20Poly1305Encrypt(plaintext: Uint8Array, nonce: Uint8Array, ad: Uint8Array, k: bytes32): bytes
15-
chaCha20Poly1305Decrypt(ciphertext: Uint8Array, nonce: Uint8Array, ad: Uint8Array, k: bytes32, dst?: Uint8Array): bytes | null
15+
chaCha20Poly1305Encrypt(plaintext: Uint8Array | Uint8ArrayList, nonce: Uint8Array, ad: Uint8Array, k: bytes32): Uint8ArrayList | Uint8Array
16+
chaCha20Poly1305Decrypt(ciphertext: Uint8Array | Uint8ArrayList, nonce: Uint8Array, ad: Uint8Array, k: bytes32, dst?: Uint8Array): Uint8ArrayList | Uint8Array | null
1617
}

src/crypto/index.ts

Lines changed: 90 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import crypto from 'node:crypto'
22
import { newInstance, ChaCha20Poly1305 } from '@chainsafe/as-chacha20poly1305'
33
import { digest } from '@chainsafe/as-sha256'
4+
import { Uint8ArrayList } from 'uint8arraylist'
45
import { isElectronMain } from 'wherearewe'
56
import { pureJsCrypto } from './js.js'
67
import type { KeyPair } from '../@types/libp2p.js'
@@ -13,50 +14,105 @@ const PKCS8_PREFIX = Buffer.from([0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06
1314
const X25519_PREFIX = Buffer.from([0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x03, 0x21, 0x00])
1415
const nodeCrypto: Pick<ICryptoInterface, 'hashSHA256' | 'chaCha20Poly1305Encrypt' | 'chaCha20Poly1305Decrypt'> = {
1516
hashSHA256 (data) {
16-
return crypto.createHash('sha256').update(data).digest()
17+
const hash = crypto.createHash('sha256')
18+
19+
if (data instanceof Uint8Array) {
20+
return hash.update(data).digest()
21+
}
22+
23+
for (const buf of data) {
24+
hash.update(buf)
25+
}
26+
27+
return hash.digest()
1728
},
1829

1930
chaCha20Poly1305Encrypt (plaintext, nonce, ad, k) {
2031
const cipher = crypto.createCipheriv(CHACHA_POLY1305, k, nonce, {
2132
authTagLength: 16
2233
})
2334
cipher.setAAD(ad, { plaintextLength: plaintext.byteLength })
24-
const updated = cipher.update(plaintext)
35+
36+
if (plaintext instanceof Uint8Array) {
37+
const updated = cipher.update(plaintext)
38+
const final = cipher.final()
39+
const tag = cipher.getAuthTag()
40+
41+
return Buffer.concat([updated, tag, final], updated.byteLength + tag.byteLength + final.byteLength)
42+
}
43+
44+
const output = new Uint8ArrayList()
45+
46+
for (const buf of plaintext) {
47+
output.append(cipher.update(buf))
48+
}
49+
2550
const final = cipher.final()
26-
const tag = cipher.getAuthTag()
2751

28-
const encrypted = Buffer.concat([updated, tag, final], updated.byteLength + tag.byteLength + final.byteLength)
29-
return encrypted
52+
if (final.byteLength > 0) {
53+
output.append(final)
54+
}
55+
56+
output.append(cipher.getAuthTag())
57+
58+
return output
3059
},
3160

3261
chaCha20Poly1305Decrypt (ciphertext, nonce, ad, k, _dst) {
3362
const authTag = ciphertext.subarray(ciphertext.length - 16)
34-
const text = ciphertext.subarray(0, ciphertext.length - 16)
3563
const decipher = crypto.createDecipheriv(CHACHA_POLY1305, k, nonce, {
3664
authTagLength: 16
3765
})
66+
67+
let text: Uint8Array | Uint8ArrayList
68+
69+
if (ciphertext instanceof Uint8Array) {
70+
text = ciphertext.subarray(0, ciphertext.length - 16)
71+
} else {
72+
text = ciphertext.sublist(0, ciphertext.length - 16)
73+
}
74+
3875
decipher.setAAD(ad, {
3976
plaintextLength: text.byteLength
4077
})
4178
decipher.setAuthTag(authTag)
42-
const updated = decipher.update(text)
79+
80+
if (text instanceof Uint8Array) {
81+
const output = decipher.update(text)
82+
const final = decipher.final()
83+
84+
if (final.byteLength > 0) {
85+
return Buffer.concat([output, final], output.byteLength + final.byteLength)
86+
}
87+
88+
return output
89+
}
90+
91+
const output = new Uint8ArrayList()
92+
93+
for (const buf of text) {
94+
output.append(decipher.update(buf))
95+
}
96+
4397
const final = decipher.final()
98+
4499
if (final.byteLength > 0) {
45-
return Buffer.concat([updated, final], updated.byteLength + final.byteLength)
100+
output.append(final)
46101
}
47-
return updated
102+
103+
return output
48104
}
49105
}
50106

51107
const asCrypto: Pick<ICryptoInterface, 'hashSHA256' | 'chaCha20Poly1305Encrypt' | 'chaCha20Poly1305Decrypt'> = {
52108
hashSHA256 (data) {
53-
return digest(data)
109+
return digest(data.subarray())
54110
},
55111
chaCha20Poly1305Encrypt (plaintext, nonce, ad, k) {
56-
return asImpl.seal(k, nonce, plaintext, ad)
112+
return asImpl.seal(k, nonce, plaintext.subarray(), ad)
57113
},
58114
chaCha20Poly1305Decrypt (ciphertext, nonce, ad, k, dst) {
59-
return asImpl.open(k, nonce, ciphertext, ad, dst)
115+
return asImpl.open(k, nonce, ciphertext.subarray(), ad, dst)
60116
}
61117
}
62118

@@ -69,13 +125,13 @@ export const defaultCrypto: ICryptoInterface = {
69125
return nodeCrypto.hashSHA256(data)
70126
},
71127
chaCha20Poly1305Encrypt (plaintext, nonce, ad, k) {
72-
if (plaintext.length < 1200) {
128+
if (plaintext.byteLength < 1200) {
73129
return asCrypto.chaCha20Poly1305Encrypt(plaintext, nonce, ad, k)
74130
}
75131
return nodeCrypto.chaCha20Poly1305Encrypt(plaintext, nonce, ad, k)
76132
},
77133
chaCha20Poly1305Decrypt (ciphertext, nonce, ad, k, dst) {
78-
if (ciphertext.length < 1200) {
134+
if (ciphertext.byteLength < 1200) {
79135
return asCrypto.chaCha20Poly1305Decrypt(ciphertext, nonce, ad, k, dst)
80136
}
81137
return nodeCrypto.chaCha20Poly1305Decrypt(ciphertext, nonce, ad, k, dst)
@@ -118,16 +174,26 @@ export const defaultCrypto: ICryptoInterface = {
118174
privateKey: seed
119175
}
120176
},
121-
generateX25519SharedKey (privateKey: Uint8Array, publicKey: Uint8Array): Uint8Array {
122-
publicKey = Buffer.concat([
123-
X25519_PREFIX,
124-
publicKey
125-
], X25519_PREFIX.byteLength + publicKey.byteLength)
126-
127-
privateKey = Buffer.concat([
128-
PKCS8_PREFIX,
129-
privateKey
130-
], PKCS8_PREFIX.byteLength + privateKey.byteLength)
177+
generateX25519SharedKey (privateKey: Uint8Array | Uint8ArrayList, publicKey: Uint8Array | Uint8ArrayList): Uint8Array {
178+
if (publicKey instanceof Uint8Array) {
179+
publicKey = Buffer.concat([
180+
X25519_PREFIX,
181+
publicKey
182+
], X25519_PREFIX.byteLength + publicKey.byteLength)
183+
} else {
184+
publicKey.prepend(X25519_PREFIX)
185+
publicKey = publicKey.subarray()
186+
}
187+
188+
if (privateKey instanceof Uint8Array) {
189+
privateKey = Buffer.concat([
190+
PKCS8_PREFIX,
191+
privateKey
192+
], PKCS8_PREFIX.byteLength + privateKey.byteLength)
193+
} else {
194+
privateKey.prepend(PKCS8_PREFIX)
195+
privateKey = privateKey.subarray()
196+
}
131197

132198
return crypto.diffieHellman({
133199
publicKey: crypto.createPublicKey({

src/crypto/js.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import { chacha20poly1305 } from '@noble/ciphers/chacha'
22
import { x25519 } from '@noble/curves/ed25519'
33
import { extract, expand } from '@noble/hashes/hkdf'
44
import { sha256 } from '@noble/hashes/sha256'
5-
import type { bytes, bytes32 } from '../@types/basic.js'
5+
import type { bytes32 } from '../@types/basic.js'
66
import type { Hkdf } from '../@types/handshake.js'
77
import type { KeyPair } from '../@types/libp2p.js'
88
import type { ICryptoInterface } from '../crypto.js'
9+
import type { Uint8ArrayList } from 'uint8arraylist'
910

1011
export const pureJsCrypto: ICryptoInterface = {
11-
hashSHA256 (data: Uint8Array): Uint8Array {
12-
return sha256(data)
12+
hashSHA256 (data: Uint8Array | Uint8ArrayList): Uint8Array {
13+
return sha256(data.subarray())
1314
},
1415

1516
getHKDF (ck: bytes32, ikm: Uint8Array): Hkdf {
@@ -43,15 +44,15 @@ export const pureJsCrypto: ICryptoInterface = {
4344
}
4445
},
4546

46-
generateX25519SharedKey (privateKey: Uint8Array, publicKey: Uint8Array): Uint8Array {
47-
return x25519.getSharedSecret(privateKey, publicKey)
47+
generateX25519SharedKey (privateKey: Uint8Array | Uint8ArrayList, publicKey: Uint8Array | Uint8ArrayList): Uint8Array {
48+
return x25519.getSharedSecret(privateKey.subarray(), publicKey.subarray())
4849
},
4950

50-
chaCha20Poly1305Encrypt (plaintext: Uint8Array, nonce: Uint8Array, ad: Uint8Array, k: bytes32): bytes {
51-
return chacha20poly1305(k, nonce, ad).encrypt(plaintext)
51+
chaCha20Poly1305Encrypt (plaintext: Uint8Array | Uint8ArrayList, nonce: Uint8Array, ad: Uint8Array, k: bytes32): Uint8Array {
52+
return chacha20poly1305(k, nonce, ad).encrypt(plaintext.subarray())
5253
},
5354

54-
chaCha20Poly1305Decrypt (ciphertext: Uint8Array, nonce: Uint8Array, ad: Uint8Array, k: bytes32, dst?: Uint8Array): bytes | null {
55-
return chacha20poly1305(k, nonce, ad).decrypt(ciphertext, dst)
55+
chaCha20Poly1305Decrypt (ciphertext: Uint8Array | Uint8ArrayList, nonce: Uint8Array, ad: Uint8Array, k: bytes32, dst?: Uint8Array): Uint8Array | null {
56+
return chacha20poly1305(k, nonce, ad).decrypt(ciphertext.subarray(), dst)
5657
}
5758
}

0 commit comments

Comments
 (0)