diff --git a/package.json b/package.json index f5e92cd..3723e77 100644 --- a/package.json +++ b/package.json @@ -71,18 +71,19 @@ "@libp2p/interface-keys": "^1.0.2", "@libp2p/interface-peer-id": "^1.0.2", "@libp2p/logger": "^2.0.0", - "@libp2p/peer-collections": "^1.0.0", + "@libp2p/peer-collections": "^2.0.0", "@libp2p/peer-id": "^1.1.8", "@stablelib/chacha20poly1305": "^1.0.1", "@stablelib/hkdf": "^1.0.1", "@stablelib/sha256": "^1.0.1", "@stablelib/x25519": "^1.0.1", - "it-length-prefixed": "^7.0.1", + "it-length-prefixed": "^8.0.2", "it-pair": "^2.0.2", - "it-pb-stream": "^1.0.2", + "it-pb-stream": "^2.0.1", "it-pipe": "^2.0.3", - "protons-runtime": "^1.0.3", - "uint8arraylist": "^1.4.0", + "it-stream-types": "^1.0.4", + "protons-runtime": "^2.0.1", + "uint8arraylist": "^2.0.0", "uint8arrays": "^3.0.0" }, "devDependencies": { @@ -90,12 +91,9 @@ "@libp2p/peer-id-factory": "^1.0.8", "aegir": "^37.3.0", "benchmark": "^2.1.4", - "events": "^3.3.0", - "microtime": "^3.0.0", "mkdirp": "^1.0.4", - "protons": "^3.0.3", - "sinon": "^14.0.0", - "util": "^0.12.4" + "protons": "^4.0.0", + "sinon": "^14.0.0" }, "browser": { "./dist/src/alloc-unsafe.js": "./dist/src/alloc-unsafe-browser.js", diff --git a/src/crypto/stablelib.ts b/src/crypto/stablelib.ts index cf6aa04..98ff24e 100644 --- a/src/crypto/stablelib.ts +++ b/src/crypto/stablelib.ts @@ -17,9 +17,9 @@ export const stablelib: ICryptoInterface = { const okmU8Array = hkdf.expand(96) const okm = okmU8Array - const k1 = okm.slice(0, 32) - const k2 = okm.slice(32, 64) - const k3 = okm.slice(64, 96) + const k1 = okm.subarray(0, 32) + const k2 = okm.subarray(32, 64) + const k3 = okm.subarray(64, 96) return [k1, k2, k3] }, diff --git a/src/crypto/streaming.ts b/src/crypto/streaming.ts index 2150257..263e0a2 100644 --- a/src/crypto/streaming.ts +++ b/src/crypto/streaming.ts @@ -1,4 +1,5 @@ import type { Transform } from 'it-stream-types' +import type { Uint8ArrayList } from 'uint8arraylist' import type { IHandshake } from '../@types/handshake-interface.js' import { NOISE_MSG_MAX_LENGTH_BYTES, NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG } from '../constants.js' @@ -12,7 +13,7 @@ export function encryptStream (handshake: IHandshake): Transform { end = chunk.length } - const data = handshake.encrypt(chunk.slice(i, end), handshake.session) + const data = handshake.encrypt(chunk.subarray(i, end), handshake.session) yield data } } @@ -20,7 +21,7 @@ export function encryptStream (handshake: IHandshake): Transform { } // Decrypt received payload to the user -export function decryptStream (handshake: IHandshake): Transform { +export function decryptStream (handshake: IHandshake): Transform { return async function * (source) { for await (const chunk of source) { for (let i = 0; i < chunk.length; i += NOISE_MSG_MAX_LENGTH_BYTES) { @@ -29,7 +30,7 @@ export function decryptStream (handshake: IHandshake): Transform { end = chunk.length } - const { plaintext: decrypted, valid } = await handshake.decrypt(chunk.slice(i, end), handshake.session) + const { plaintext: decrypted, valid } = await handshake.decrypt(chunk.subarray(i, end), handshake.session) if (!valid) { throw new Error('Failed to validate decrypted chunk') } diff --git a/src/encoder.ts b/src/encoder.ts index 2ff7cfa..1345d2e 100644 --- a/src/encoder.ts +++ b/src/encoder.ts @@ -2,6 +2,7 @@ import { concat as uint8ArrayConcat } from 'uint8arrays/concat' import type { Uint8ArrayList } from 'uint8arraylist' import type { bytes } from './@types/basic.js' import type { MessageBuffer } from './@types/handshake.js' +import type { LengthDecoderFunction, LengthEncoderFunction } from 'it-length-prefixed' const allocUnsafe = (len: number): Uint8Array => { if (globalThis.Buffer) { @@ -11,14 +12,14 @@ const allocUnsafe = (len: number): Uint8Array => { return new Uint8Array(len) } -export const uint16BEEncode = (value: number, target?: Uint8Array, offset?: number): Uint8Array => { - target = target ?? allocUnsafe(2) - new DataView(target.buffer, target.byteOffset, target.byteLength).setUint16(offset ?? 0, value, false) +export const uint16BEEncode: LengthEncoderFunction = (value: number) => { + const target = allocUnsafe(2) + new DataView(target.buffer, target.byteOffset, target.byteLength).setUint16(0, value, false) return target } uint16BEEncode.bytes = 2 -export const uint16BEDecode = (data: Uint8Array | Uint8ArrayList): number => { +export const uint16BEDecode: LengthDecoderFunction = (data: Uint8Array | Uint8ArrayList): number => { if (data.length < 2) throw RangeError('Could not decode int16BE') if (data instanceof Uint8Array) { @@ -49,8 +50,8 @@ export function decode0 (input: bytes): MessageBuffer { } return { - ne: input.slice(0, 32), - ciphertext: input.slice(32, input.length), + ne: input.subarray(0, 32), + ciphertext: input.subarray(32, input.length), ns: new Uint8Array(0) } } @@ -61,9 +62,9 @@ export function decode1 (input: bytes): MessageBuffer { } return { - ne: input.slice(0, 32), - ns: input.slice(32, 80), - ciphertext: input.slice(80, input.length) + ne: input.subarray(0, 32), + ns: input.subarray(32, 80), + ciphertext: input.subarray(80, input.length) } } @@ -74,7 +75,7 @@ export function decode2 (input: bytes): MessageBuffer { return { ne: new Uint8Array(0), - ns: input.slice(0, 48), - ciphertext: input.slice(48, input.length) + ns: input.subarray(0, 48), + ciphertext: input.subarray(48, input.length) } } diff --git a/src/handshake-ik.ts b/src/handshake-ik.ts index bf3851f..e48a8af 100644 --- a/src/handshake-ik.ts +++ b/src/handshake-ik.ts @@ -83,7 +83,7 @@ export class IKHandshake implements IHandshake { const err = e as Error logger('Responder breaking up with IK handshake in stage 0.') - throw new FailedIKError(receivedMsg, `Error occurred while verifying initiator's signed payload: ${err.message}`) + throw new FailedIKError(receivedMsg.slice(), `Error occurred while verifying initiator's signed payload: ${err.message}`) } } } diff --git a/src/handshake-xx.ts b/src/handshake-xx.ts index cec4b3a..9139b12 100644 --- a/src/handshake-xx.ts +++ b/src/handshake-xx.ts @@ -69,7 +69,7 @@ export class XXHandshake implements IHandshake { logLocalEphemeralKeys(this.session.hs.e) } else { logger('Stage 0 - Responder waiting to receive first message...') - const receivedMessageBuffer = decode0((await this.connection.readLP()).slice()) + const receivedMessageBuffer = decode0((await this.connection.readLP()).subarray()) const { valid } = this.xx.recvMessage(this.session, receivedMessageBuffer) if (!valid) { throw new InvalidCryptoExchangeError('xx handshake stage 0 validation fail') @@ -83,7 +83,7 @@ export class XXHandshake implements IHandshake { public async exchange (): Promise { if (this.isInitiator) { logger('Stage 1 - Initiator waiting to receive first message from responder...') - const receivedMessageBuffer = decode1((await this.connection.readLP()).slice()) + const receivedMessageBuffer = decode1((await this.connection.readLP()).subarray()) const { plaintext, valid } = this.xx.recvMessage(this.session, receivedMessageBuffer) if (!valid) { throw new InvalidCryptoExchangeError('xx handshake stage 1 validation fail') @@ -121,7 +121,7 @@ export class XXHandshake implements IHandshake { logger('Stage 2 - Initiator sent message with signed payload.') } else { logger('Stage 2 - Responder waiting for third handshake message...') - const receivedMessageBuffer = decode2((await this.connection.readLP()).slice()) + const receivedMessageBuffer = decode2((await this.connection.readLP()).subarray()) const { plaintext, valid } = this.xx.recvMessage(this.session, receivedMessageBuffer) if (!valid) { throw new InvalidCryptoExchangeError('xx handshake stage 2 validation fail') diff --git a/src/handshakes/abstract-handshake.ts b/src/handshakes/abstract-handshake.ts index 9cd95e7..576957b 100644 --- a/src/handshakes/abstract-handshake.ts +++ b/src/handshakes/abstract-handshake.ts @@ -98,7 +98,7 @@ export abstract class AbstractHandshake { return derivedU8 } - return derivedU8.slice(0, 32) + return derivedU8.subarray(0, 32) } catch (e) { const err = e as Error logger(err.message) diff --git a/src/proto/payload.ts b/src/proto/payload.ts index 855a319..1c75bc4 100644 --- a/src/proto/payload.ts +++ b/src/proto/payload.ts @@ -3,6 +3,7 @@ import { encodeMessage, decodeMessage, message, bytes } from 'protons-runtime' import type { Codec } from 'protons-runtime' +import type { Uint8ArrayList } from 'uint8arraylist' export namespace pb { export interface NoiseHandshakePayload { @@ -20,11 +21,11 @@ export namespace pb { }) } - export const encode = (obj: NoiseHandshakePayload): Uint8Array => { + export const encode = (obj: NoiseHandshakePayload): Uint8ArrayList => { return encodeMessage(obj, NoiseHandshakePayload.codec()) } - export const decode = (buf: Uint8Array): NoiseHandshakePayload => { + export const decode = (buf: Uint8Array | Uint8ArrayList): NoiseHandshakePayload => { return decodeMessage(buf, NoiseHandshakePayload.codec()) } } diff --git a/src/utils.ts b/src/utils.ts index cc9ff33..77d5388 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -36,7 +36,7 @@ export function createHandshakePayload ( identityKey: libp2pPublicKey, identitySig: signedPayload, data: earlyData ?? new Uint8Array(0) - }) + }).subarray() } export async function signPayload (peerId: PeerId, payload: bytes): Promise { diff --git a/test/noise.spec.ts b/test/noise.spec.ts index 92dbc8e..1d7fde3 100644 --- a/test/noise.spec.ts +++ b/test/noise.spec.ts @@ -6,7 +6,7 @@ import { duplexPair } from 'it-pair/duplex' import { pbStream } from 'it-pb-stream' import { equals as uint8ArrayEquals } from 'uint8arrays/equals' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' -import { Uint8ArrayList } from 'uint8arraylist' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import sinon from 'sinon' import { NOISE_MSG_MAX_LENGTH_BYTES } from '../src/constants.js' import { stablelib } from '../src/crypto/stablelib.js' @@ -95,23 +95,15 @@ describe('Noise', () => { })() ]) - try { - const wrappedOutbound = pbStream(outbound.conn) - wrappedOutbound.write(new Uint8ArrayList(Buffer.from('test'))) - - // Check that noise message is prefixed with 16-bit big-endian unsigned integer - const receivedEncryptedPayload = (await wrapped.read()).slice() - const view = new DataView(receivedEncryptedPayload.buffer, receivedEncryptedPayload.byteOffset, receivedEncryptedPayload.byteLength) - const dataLength = view.getInt16(0) - const data = receivedEncryptedPayload.slice(2, dataLength + 2) - const { plaintext: decrypted, valid } = handshake.decrypt(data, handshake.session) - // Decrypted data should match - assert(uint8ArrayEquals(decrypted, Buffer.from('test'))) - assert(valid) - } catch (e) { - const err = e as Error - assert(false, err.message) - } + const wrappedOutbound = pbStream(outbound.conn) + wrappedOutbound.write(uint8ArrayFromString('test')) + + // Check that noise message is prefixed with 16-bit big-endian unsigned integer + const data = await (await wrapped.readLP()).slice() + const { plaintext: decrypted, valid } = handshake.decrypt(data, handshake.session) + // Decrypted data should match + expect(uint8ArrayEquals(decrypted, uint8ArrayFromString('test'))).to.be.true() + expect(valid).to.be.true() }) it('should test large payloads', async function () { diff --git a/test/utils.ts b/test/utils.ts index a2cfa74..a03a5b5 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,7 +1,6 @@ import { keys } from '@libp2p/crypto' import type { PrivateKey } from '@libp2p/interface-keys' import type { PeerId } from '@libp2p/interface-peer-id' -import { Buffer } from 'buffer' import type { KeyPair } from '../src/@types/libp2p.js' export async function generateEd25519Keys (): Promise { @@ -14,7 +13,7 @@ export function getKeyPairFromPeerId (peerId: PeerId): KeyPair { } return { - privateKey: Buffer.from(peerId.privateKey.slice(0, 32)), - publicKey: Buffer.from(peerId.publicKey) + privateKey: peerId.privateKey.subarray(0, 32), + publicKey: peerId.publicKey } }