From 1100e295b93c667d9fb876f75e54ca99a6255855 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Tue, 27 Sep 2022 14:54:33 -0700 Subject: [PATCH 1/6] feat: Add Noise Extensions --- package.json | 2 +- src/@types/handshake-interface.ts | 4 +- src/handshake-xx.ts | 10 ++ src/noise.ts | 15 +- src/proto/payload.proto | 16 ++- src/proto/payload.ts | 230 ++++++++++++++++++++---------- src/utils.ts | 36 +++-- test/interop.ts | 12 +- test/noise.spec.ts | 15 +- 9 files changed, 219 insertions(+), 121 deletions(-) diff --git a/package.json b/package.json index 4d1ff80..e470c38 100644 --- a/package.json +++ b/package.json @@ -111,4 +111,4 @@ "./dist/src/alloc-unsafe.js": "./dist/src/alloc-unsafe-browser.js", "util": false } -} +} \ No newline at end of file diff --git a/src/@types/handshake-interface.ts b/src/@types/handshake-interface.ts index 485a11a..aef9066 100644 --- a/src/@types/handshake-interface.ts +++ b/src/@types/handshake-interface.ts @@ -1,11 +1,13 @@ import type { PeerId } from '@libp2p/interface-peer-id' import type { bytes } from './basic.js' import type { NoiseSession } from './handshake.js' +import type { NoiseExtensions } from '../proto/payload.js' export interface IHandshake { session: NoiseSession remotePeer: PeerId remoteEarlyData: bytes + remoteExtensions: NoiseExtensions encrypt: (plaintext: bytes, session: NoiseSession) => bytes - decrypt: (ciphertext: bytes, session: NoiseSession) => {plaintext: bytes, valid: boolean} + decrypt: (ciphertext: bytes, session: NoiseSession) => { plaintext: bytes, valid: boolean } } diff --git a/src/handshake-xx.ts b/src/handshake-xx.ts index 9139b12..db11869 100644 --- a/src/handshake-xx.ts +++ b/src/handshake-xx.ts @@ -21,12 +21,14 @@ import { getPeerIdFromPayload, verifySignedPayload } from './utils.js' +import type { NoiseExtensions } from './proto/payload.js' export class XXHandshake implements IHandshake { public isInitiator: boolean public session: NoiseSession public remotePeer!: PeerId public remoteEarlyData: bytes + public remoteExtensions: NoiseExtensions = { webtransportCerthashes: [] } protected payload: bytes protected connection: ProtobufStream @@ -98,6 +100,7 @@ export class XXHandshake implements IHandshake { this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload) await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer) this.setRemoteEarlyData(decodedPayload.data) + this.setRemoteNoiseExtension(decodedPayload.extensions) } catch (e) { const err = e as Error throw new UnexpectedPeerError(`Error occurred while verifying signed payload: ${err.message}`) @@ -133,6 +136,7 @@ export class XXHandshake implements IHandshake { this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload) await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer) this.setRemoteEarlyData(decodedPayload.data) + this.setRemoteNoiseExtension(decodedPayload.extensions) } catch (e) { const err = e as Error throw new UnexpectedPeerError(`Error occurred while verifying signed payload: ${err.message}`) @@ -174,4 +178,10 @@ export class XXHandshake implements IHandshake { this.remoteEarlyData = data } } + + protected setRemoteNoiseExtension(e: NoiseExtensions | null | undefined): void { + if (e) { + this.remoteExtensions = e + } + } } diff --git a/src/noise.ts b/src/noise.ts index b2d08c2..b389eaf 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -15,6 +15,7 @@ import { decryptStream, encryptStream } from './crypto/streaming.js' import { uint16BEDecode, uint16BEEncode } from './encoder.js' import { XXHandshake } from './handshake-xx.js' import { getPayload } from './utils.js' +import type { NoiseExtensions } from './proto/payload.js' interface HandshakeParams { connection: ProtobufStream @@ -29,15 +30,15 @@ export class Noise implements INoiseConnection { private readonly prologue: Uint8Array private readonly staticKeys: KeyPair - private readonly earlyData?: bytes + private readonly extensions?: NoiseExtensions /** * @param {bytes} staticNoiseKey - x25519 private key, reuse for faster handshakes * @param {bytes} earlyData */ - constructor (staticNoiseKey?: bytes, earlyData?: bytes, crypto: ICryptoInterface = stablelib, prologueBytes?: Uint8Array) { - this.earlyData = earlyData ?? new Uint8Array(0) + constructor(staticNoiseKey?: bytes, extensions?: NoiseExtensions, crypto: ICryptoInterface = stablelib, prologueBytes?: Uint8Array) { this.crypto = crypto + this.extensions = extensions if (staticNoiseKey) { // accepts x25519 private key of length 32 @@ -76,6 +77,8 @@ export class Noise implements INoiseConnection { return { conn, remoteEarlyData: handshake.remoteEarlyData, + // @ts-ignore this isn't part of the interface yet. Fix me when https://github.com/libp2p/js-libp2p-interfaces/issues/291 is fixed + remoteExtensions: handshake.remoteExtensions, remotePeer: handshake.remotePeer } } @@ -88,7 +91,7 @@ export class Noise implements INoiseConnection { * @param {PeerId} remotePeer - optional PeerId of the initiating peer, if known. This may only exist during transport upgrades. * @returns {Promise} */ - public async secureInbound (localPeer: PeerId, connection: Duplex, remotePeer?: PeerId): Promise { + public async secureInbound(localPeer: PeerId, connection: Duplex, remotePeer?: PeerId): Promise { const wrappedConnection = pbStream( connection, { @@ -108,7 +111,9 @@ export class Noise implements INoiseConnection { return { conn, remoteEarlyData: handshake.remoteEarlyData, - remotePeer: handshake.remotePeer + remotePeer: handshake.remotePeer, + // @ts-ignore this isn't part of the interface yet. Fix me when https://github.com/libp2p/js-libp2p-interfaces/issues/291 is fixed + remoteExtensions: handshake.remoteExtensions, } } diff --git a/src/proto/payload.proto b/src/proto/payload.proto index 05a78c6..d4a6f29 100644 --- a/src/proto/payload.proto +++ b/src/proto/payload.proto @@ -1,8 +1,12 @@ -syntax = "proto3"; -package pb; +syntax = "proto2"; -message NoiseHandshakePayload { - bytes identity_key = 1; - bytes identity_sig = 2; - bytes data = 3; +message NoiseExtensions { + repeated bytes webtransport_certhashes = 1; } + +message NoiseHandshakePayload { + optional bytes identity_key = 1; + optional bytes identity_sig = 2; + optional bytes data = 3; + optional NoiseExtensions extensions = 4; +} \ No newline at end of file diff --git a/src/proto/payload.ts b/src/proto/payload.ts index 0d1af33..e6a2d8b 100644 --- a/src/proto/payload.ts +++ b/src/proto/payload.ts @@ -5,100 +5,176 @@ import { encodeMessage, decodeMessage, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' import type { Codec } from 'protons-runtime' -export namespace pb { - export interface NoiseHandshakePayload { - identityKey: Uint8Array - identitySig: Uint8Array - data: Uint8Array - } +export interface NoiseExtensions { + webtransportCerthashes: Uint8Array[] +} - export namespace NoiseHandshakePayload { - let _codec: Codec +export namespace NoiseExtensions { + let _codec: Codec - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { - if (opts.lengthDelimited !== false) { - writer.fork() - } + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, writer, opts = {}) => { + if (opts.lengthDelimited !== false) { + writer.fork() + } - if (obj.identityKey != null) { + if (obj.webtransportCerthashes != null) { + for (const value of obj.webtransportCerthashes) { writer.uint32(10) - writer.bytes(obj.identityKey) - } else { - throw new Error('Protocol error: required field "identityKey" was not found in object') + writer.bytes(value) } - - if (obj.identitySig != null) { - writer.uint32(18) - writer.bytes(obj.identitySig) - } else { - throw new Error('Protocol error: required field "identitySig" was not found in object') + } else { + throw new Error('Protocol error: required field "webtransportCerthashes" was not found in object') + } + + if (opts.lengthDelimited !== false) { + writer.ldelim() + } + }, (reader, length) => { + const obj: any = { + webtransportCerthashes: [] + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.webtransportCerthashes.push(reader.bytes()) + break + default: + reader.skipType(tag & 7) + break } + } - if (obj.data != null) { - writer.uint32(26) - writer.bytes(obj.data) - } else { - throw new Error('Protocol error: required field "data" was not found in object') - } + return obj + }) + } - if (opts.lengthDelimited !== false) { - writer.ldelim() - } - }, (reader, length) => { - const obj: any = { - identityKey: new Uint8Array(0), - identitySig: new Uint8Array(0), - data: new Uint8Array(0) - } + return _codec + } - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.identityKey = reader.bytes() - break - case 2: - obj.identitySig = reader.bytes() - break - case 3: - obj.data = reader.bytes() - break - default: - reader.skipType(tag & 7) - break - } - } + export const encode = (obj: NoiseExtensions): Uint8Array => { + return encodeMessage(obj, NoiseExtensions.codec()) + } - if (obj.identityKey == null) { - throw new Error('Protocol error: value for required field "identityKey" was not found in protobuf') - } + export const decode = (buf: Uint8Array | Uint8ArrayList): NoiseExtensions => { + return decodeMessage(buf, NoiseExtensions.codec()) + } +} - if (obj.identitySig == null) { - throw new Error('Protocol error: value for required field "identitySig" was not found in protobuf') - } +export interface NoiseHandshakePayload { + identityKey: Uint8Array + identitySig: Uint8Array + data: Uint8Array + extensions: NoiseExtensions +} - if (obj.data == null) { - throw new Error('Protocol error: value for required field "data" was not found in protobuf') +export namespace NoiseHandshakePayload { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, writer, opts = {}) => { + if (opts.lengthDelimited !== false) { + writer.fork() + } + + if (obj.identityKey != null) { + writer.uint32(10) + writer.bytes(obj.identityKey) + } else { + throw new Error('Protocol error: required field "identityKey" was not found in object') + } + + if (obj.identitySig != null) { + writer.uint32(18) + writer.bytes(obj.identitySig) + } else { + throw new Error('Protocol error: required field "identitySig" was not found in object') + } + + if (obj.data != null) { + writer.uint32(26) + writer.bytes(obj.data) + } else { + throw new Error('Protocol error: required field "data" was not found in object') + } + + if (obj.extensions != null) { + writer.uint32(34) + NoiseExtensions.codec().encode(obj.extensions, writer) + } else { + throw new Error('Protocol error: required field "extensions" was not found in object') + } + + if (opts.lengthDelimited !== false) { + writer.ldelim() + } + }, (reader, length) => { + const obj: any = { + identityKey: new Uint8Array(0), + identitySig: new Uint8Array(0), + data: new Uint8Array(0), + extensions: undefined + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.identityKey = reader.bytes() + break + case 2: + obj.identitySig = reader.bytes() + break + case 3: + obj.data = reader.bytes() + break + case 4: + obj.extensions = NoiseExtensions.codec().decode(reader, reader.uint32()) + break + default: + reader.skipType(tag & 7) + break } + } - return obj - }) - } + if (obj.identityKey == null) { + throw new Error('Protocol error: value for required field "identityKey" was not found in protobuf') + } - return _codec - } + if (obj.identitySig == null) { + throw new Error('Protocol error: value for required field "identitySig" was not found in protobuf') + } - export const encode = (obj: NoiseHandshakePayload): Uint8Array => { - return encodeMessage(obj, NoiseHandshakePayload.codec()) - } + if (obj.data == null) { + throw new Error('Protocol error: value for required field "data" was not found in protobuf') + } - export const decode = (buf: Uint8Array | Uint8ArrayList): NoiseHandshakePayload => { - return decodeMessage(buf, NoiseHandshakePayload.codec()) + if (obj.extensions == null) { + throw new Error('Protocol error: value for required field "extensions" was not found in protobuf') + } + + return obj + }) } + + return _codec + } + + export const encode = (obj: NoiseHandshakePayload): Uint8Array => { + return encodeMessage(obj, NoiseHandshakePayload.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): NoiseHandshakePayload => { + return decodeMessage(buf, NoiseHandshakePayload.codec()) } } diff --git a/src/utils.ts b/src/utils.ts index 77d5388..66c67ae 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -4,17 +4,14 @@ import { peerIdFromKeys } from '@libp2p/peer-id' import { concat as uint8ArrayConcat } from 'uint8arrays/concat' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import type { bytes } from './@types/basic.js' -import { pb } from './proto/payload.js' +import { NoiseExtensions, NoiseHandshakePayload } from './proto/payload.js' -const NoiseHandshakePayloadProto = pb.NoiseHandshakePayload - -export async function getPayload ( +export async function getPayload( localPeer: PeerId, staticPublicKey: bytes, - earlyData?: bytes + extensions?: NoiseExtensions ): Promise { const signedPayload = await signPayload(localPeer, getHandshakePayload(staticPublicKey)) - const earlyDataPayload = earlyData ?? new Uint8Array(0) if (localPeer.publicKey == null) { throw new Error('PublicKey was missing from local PeerId') @@ -23,23 +20,24 @@ export async function getPayload ( return createHandshakePayload( localPeer.publicKey, signedPayload, - earlyDataPayload + extensions ) } -export function createHandshakePayload ( +export function createHandshakePayload( libp2pPublicKey: Uint8Array, signedPayload: Uint8Array, - earlyData?: Uint8Array + extensions?: NoiseExtensions ): bytes { - return NoiseHandshakePayloadProto.encode({ + return NoiseHandshakePayload.encode({ identityKey: libp2pPublicKey, identitySig: signedPayload, - data: earlyData ?? new Uint8Array(0) + data: new Uint8Array(0), + extensions: extensions ?? { webtransportCerthashes: [] }, }).subarray() } -export async function signPayload (peerId: PeerId, payload: bytes): Promise { +export async function signPayload(peerId: PeerId, payload: bytes): Promise { if (peerId.privateKey == null) { throw new Error('PrivateKey was missing from PeerId') } @@ -49,15 +47,15 @@ export async function signPayload (peerId: PeerId, payload: bytes): Promise { +export async function getPeerIdFromPayload(payload: NoiseHandshakePayload): Promise { return await peerIdFromKeys(payload.identityKey) } -export function decodePayload (payload: bytes|Uint8Array): pb.NoiseHandshakePayload { - return NoiseHandshakePayloadProto.decode(payload) +export function decodePayload(payload: bytes | Uint8Array): NoiseHandshakePayload { + return NoiseHandshakePayload.decode(payload) } -export function getHandshakePayload (publicKey: bytes): bytes { +export function getHandshakePayload(publicKey: bytes): bytes { const prefix = uint8ArrayFromString('noise-libp2p-static-key:') return uint8ArrayConcat([prefix, publicKey], prefix.length + publicKey.length) } @@ -70,9 +68,9 @@ export function getHandshakePayload (publicKey: bytes): bytes { * @param {PeerId} remotePeer - owner's libp2p peer ID * @returns {Promise} - peer ID of payload owner */ -export async function verifySignedPayload ( +export async function verifySignedPayload( noiseStaticKey: bytes, - payload: pb.NoiseHandshakePayload, + payload: NoiseHandshakePayload, remotePeer: PeerId ): Promise { // Unmarshaling from PublicKey protobuf @@ -101,7 +99,7 @@ export async function verifySignedPayload ( return payloadPeerId } -export function isValidPublicKey (pk: bytes): boolean { +export function isValidPublicKey(pk: bytes): boolean { if (!(pk instanceof Uint8Array)) { return false } diff --git a/test/interop.ts b/test/interop.ts index 65b23a5..e096192 100644 --- a/test/interop.ts +++ b/test/interop.ts @@ -16,15 +16,15 @@ import type { PeerId } from '@libp2p/interface-peer-id' import { peerIdFromKeys } from '@libp2p/peer-id' import { Noise } from '../src/index.js' -async function createGoPeer (options: SpawnOptions): Promise { +async function createGoPeer(options: SpawnOptions): Promise { const controlPort = Math.floor(Math.random() * (50000 - 10000 + 1)) + 10000 const apiAddr = multiaddr(`/ip4/0.0.0.0/tcp/${controlPort}`) const log = logger(`go-libp2p:${controlPort}`) const opts = [ - `-listen=${apiAddr.toString()}`, - '-hostAddrs=/ip4/0.0.0.0/tcp/0' + `-listen=${apiAddr.toString()}`, + '-hostAddrs=/ip4/0.0.0.0/tcp/0' ] if (options.noise === true) { @@ -62,7 +62,7 @@ async function createGoPeer (options: SpawnOptions): Promise { } } -async function createJsPeer (options: SpawnOptions): Promise { +async function createJsPeer(options: SpawnOptions): Promise { let peerId: PeerId | undefined if (options.key != null) { @@ -94,9 +94,9 @@ async function createJsPeer (options: SpawnOptions): Promise { } } -async function main (): Promise { +async function main(): Promise { const factory: DaemonFactory = { - async spawn (options: SpawnOptions) { + async spawn(options: SpawnOptions) { if (options.type === 'go') { return await createGoPeer(options) } diff --git a/test/noise.spec.ts b/test/noise.spec.ts index b446650..c0c1619 100644 --- a/test/noise.spec.ts +++ b/test/noise.spec.ts @@ -162,13 +162,14 @@ describe('Noise', () => { } }) - it('should accept and return early data from remote peer', async () => { + it('should accept and return Noise extension from remote peer', async () => { try { - const localPeerEarlyData = Buffer.from('early data') + const certhashInit = Buffer.from('certhash data from init') const staticKeysInitiator = stablelib.generateX25519KeyPair() - const noiseInit = new Noise(staticKeysInitiator.privateKey, localPeerEarlyData) + const noiseInit = new Noise(staticKeysInitiator.privateKey, { webtransportCerthashes: [certhashInit] }) const staticKeysResponder = stablelib.generateX25519KeyPair() - const noiseResp = new Noise(staticKeysResponder.privateKey) + const certhashResp = Buffer.from('certhash data from respon') + const noiseResp = new Noise(staticKeysResponder.privateKey, { webtransportCerthashes: [certhashResp] }) const [inboundConnection, outboundConnection] = duplexPair() const [outbound, inbound] = await Promise.all([ @@ -176,8 +177,10 @@ describe('Noise', () => { noiseResp.secureInbound(remotePeer, inboundConnection) ]) - assert(uint8ArrayEquals(inbound.remoteEarlyData, localPeerEarlyData)) - assert(uint8ArrayEquals(outbound.remoteEarlyData, Buffer.alloc(0))) + // @ts-ignore https://github.com/libp2p/js-libp2p-interfaces/issues/291 + assert(uint8ArrayEquals(inbound.remoteExtensions.webtransportCerthashes[0], certhashInit)) + // @ts-ignore https://github.com/libp2p/js-libp2p-interfaces/issues/291 + assert(uint8ArrayEquals(outbound.remoteExtensions.webtransportCerthashes[0], certhashResp)) } catch (e) { const err = e as Error assert(false, err.message) From 8bd44f219b31bf62d4f1b679b0a88bfc7b611ab5 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Mon, 3 Oct 2022 16:19:42 -0700 Subject: [PATCH 2/6] Remove remoteEarlyData and lint:fix --- src/@types/handshake-interface.ts | 1 - src/@types/libp2p.ts | 6 ++---- src/handshake-xx.ts | 14 ++------------ src/noise.ts | 16 +++++++--------- src/proto/payload.proto | 1 - src/proto/payload.ts | 16 ---------------- src/utils.ts | 18 +++++++++--------- test/interop.ts | 8 ++++---- test/noise.spec.ts | 4 ++-- 9 files changed, 26 insertions(+), 58 deletions(-) diff --git a/src/@types/handshake-interface.ts b/src/@types/handshake-interface.ts index aef9066..8fe4072 100644 --- a/src/@types/handshake-interface.ts +++ b/src/@types/handshake-interface.ts @@ -6,7 +6,6 @@ import type { NoiseExtensions } from '../proto/payload.js' export interface IHandshake { session: NoiseSession remotePeer: PeerId - remoteEarlyData: bytes remoteExtensions: NoiseExtensions encrypt: (plaintext: bytes, session: NoiseSession) => bytes decrypt: (ciphertext: bytes, session: NoiseSession) => { plaintext: bytes, valid: boolean } diff --git a/src/@types/libp2p.ts b/src/@types/libp2p.ts index 4730297..e062802 100644 --- a/src/@types/libp2p.ts +++ b/src/@types/libp2p.ts @@ -1,11 +1,9 @@ import type { ConnectionEncrypter } from '@libp2p/interface-connection-encrypter' -import type { bytes, bytes32 } from './basic.js' +import type { bytes32 } from './basic.js' export interface KeyPair { publicKey: bytes32 privateKey: bytes32 } -export interface INoiseConnection extends ConnectionEncrypter { - remoteEarlyData?: () => bytes -} +export interface INoiseConnection extends ConnectionEncrypter {} diff --git a/src/handshake-xx.ts b/src/handshake-xx.ts index db11869..2f2275b 100644 --- a/src/handshake-xx.ts +++ b/src/handshake-xx.ts @@ -27,7 +27,6 @@ export class XXHandshake implements IHandshake { public isInitiator: boolean public session: NoiseSession public remotePeer!: PeerId - public remoteEarlyData: bytes public remoteExtensions: NoiseExtensions = { webtransportCerthashes: [] } protected payload: bytes @@ -57,7 +56,6 @@ export class XXHandshake implements IHandshake { } this.xx = handshake ?? new XX(crypto) this.session = this.xx.initSession(this.isInitiator, this.prologue, this.staticKeypair) - this.remoteEarlyData = new Uint8Array(0) } // stage 0 @@ -99,7 +97,6 @@ export class XXHandshake implements IHandshake { const decodedPayload = decodePayload(plaintext) this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload) await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer) - this.setRemoteEarlyData(decodedPayload.data) this.setRemoteNoiseExtension(decodedPayload.extensions) } catch (e) { const err = e as Error @@ -135,7 +132,6 @@ export class XXHandshake implements IHandshake { const decodedPayload = decodePayload(plaintext) this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload) await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer) - this.setRemoteEarlyData(decodedPayload.data) this.setRemoteNoiseExtension(decodedPayload.extensions) } catch (e) { const err = e as Error @@ -151,7 +147,7 @@ export class XXHandshake implements IHandshake { return this.xx.encryptWithAd(cs, new Uint8Array(0), plaintext) } - public decrypt (ciphertext: Uint8Array, session: NoiseSession): {plaintext: bytes, valid: boolean} { + public decrypt (ciphertext: Uint8Array, session: NoiseSession): { plaintext: bytes, valid: boolean } { const cs = this.getCS(session, false) return this.xx.decryptWithAd(cs, new Uint8Array(0), ciphertext) @@ -173,13 +169,7 @@ export class XXHandshake implements IHandshake { } } - protected setRemoteEarlyData (data: Uint8Array|null|undefined): void { - if (data) { - this.remoteEarlyData = data - } - } - - protected setRemoteNoiseExtension(e: NoiseExtensions | null | undefined): void { + protected setRemoteNoiseExtension (e: NoiseExtensions | null | undefined): void { if (e) { this.remoteExtensions = e } diff --git a/src/noise.ts b/src/noise.ts index b389eaf..a67662d 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -34,9 +34,9 @@ export class Noise implements INoiseConnection { /** * @param {bytes} staticNoiseKey - x25519 private key, reuse for faster handshakes - * @param {bytes} earlyData + * @param {NoiseExtensions} extensions */ - constructor(staticNoiseKey?: bytes, extensions?: NoiseExtensions, crypto: ICryptoInterface = stablelib, prologueBytes?: Uint8Array) { + constructor (staticNoiseKey?: bytes, extensions?: NoiseExtensions, crypto: ICryptoInterface = stablelib, prologueBytes?: Uint8Array) { this.crypto = crypto this.extensions = extensions @@ -76,8 +76,7 @@ export class Noise implements INoiseConnection { return { conn, - remoteEarlyData: handshake.remoteEarlyData, - // @ts-ignore this isn't part of the interface yet. Fix me when https://github.com/libp2p/js-libp2p-interfaces/issues/291 is fixed + // @ts-expect-error this isn't part of the interface yet. Fix me when https://github.com/libp2p/js-libp2p-interfaces/issues/291 is fixed remoteExtensions: handshake.remoteExtensions, remotePeer: handshake.remotePeer } @@ -91,7 +90,7 @@ export class Noise implements INoiseConnection { * @param {PeerId} remotePeer - optional PeerId of the initiating peer, if known. This may only exist during transport upgrades. * @returns {Promise} */ - public async secureInbound(localPeer: PeerId, connection: Duplex, remotePeer?: PeerId): Promise { + public async secureInbound (localPeer: PeerId, connection: Duplex, remotePeer?: PeerId): Promise { const wrappedConnection = pbStream( connection, { @@ -110,10 +109,9 @@ export class Noise implements INoiseConnection { return { conn, - remoteEarlyData: handshake.remoteEarlyData, remotePeer: handshake.remotePeer, - // @ts-ignore this isn't part of the interface yet. Fix me when https://github.com/libp2p/js-libp2p-interfaces/issues/291 is fixed - remoteExtensions: handshake.remoteExtensions, + // @ts-expect-error this isn't part of the interface yet. Fix me when https://github.com/libp2p/js-libp2p-interfaces/issues/291 is fixed + remoteExtensions: handshake.remoteExtensions } } @@ -124,7 +122,7 @@ export class Noise implements INoiseConnection { * @param {HandshakeParams} params */ private async performHandshake (params: HandshakeParams): Promise { - const payload = await getPayload(params.localPeer, this.staticKeys.publicKey, this.earlyData) + const payload = await getPayload(params.localPeer, this.staticKeys.publicKey, this.extensions) // run XX handshake return await this.performXXHandshake(params, payload) diff --git a/src/proto/payload.proto b/src/proto/payload.proto index d4a6f29..1a55cd7 100644 --- a/src/proto/payload.proto +++ b/src/proto/payload.proto @@ -7,6 +7,5 @@ message NoiseExtensions { message NoiseHandshakePayload { optional bytes identity_key = 1; optional bytes identity_sig = 2; - optional bytes data = 3; optional NoiseExtensions extensions = 4; } \ No newline at end of file diff --git a/src/proto/payload.ts b/src/proto/payload.ts index e6a2d8b..b662af1 100644 --- a/src/proto/payload.ts +++ b/src/proto/payload.ts @@ -70,7 +70,6 @@ export namespace NoiseExtensions { export interface NoiseHandshakePayload { identityKey: Uint8Array identitySig: Uint8Array - data: Uint8Array extensions: NoiseExtensions } @@ -98,13 +97,6 @@ export namespace NoiseHandshakePayload { throw new Error('Protocol error: required field "identitySig" was not found in object') } - if (obj.data != null) { - writer.uint32(26) - writer.bytes(obj.data) - } else { - throw new Error('Protocol error: required field "data" was not found in object') - } - if (obj.extensions != null) { writer.uint32(34) NoiseExtensions.codec().encode(obj.extensions, writer) @@ -119,7 +111,6 @@ export namespace NoiseHandshakePayload { const obj: any = { identityKey: new Uint8Array(0), identitySig: new Uint8Array(0), - data: new Uint8Array(0), extensions: undefined } @@ -135,9 +126,6 @@ export namespace NoiseHandshakePayload { case 2: obj.identitySig = reader.bytes() break - case 3: - obj.data = reader.bytes() - break case 4: obj.extensions = NoiseExtensions.codec().decode(reader, reader.uint32()) break @@ -155,10 +143,6 @@ export namespace NoiseHandshakePayload { throw new Error('Protocol error: value for required field "identitySig" was not found in protobuf') } - if (obj.data == null) { - throw new Error('Protocol error: value for required field "data" was not found in protobuf') - } - if (obj.extensions == null) { throw new Error('Protocol error: value for required field "extensions" was not found in protobuf') } diff --git a/src/utils.ts b/src/utils.ts index 66c67ae..7e8b8d2 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -6,7 +6,7 @@ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import type { bytes } from './@types/basic.js' import { NoiseExtensions, NoiseHandshakePayload } from './proto/payload.js' -export async function getPayload( +export async function getPayload ( localPeer: PeerId, staticPublicKey: bytes, extensions?: NoiseExtensions @@ -24,7 +24,7 @@ export async function getPayload( ) } -export function createHandshakePayload( +export function createHandshakePayload ( libp2pPublicKey: Uint8Array, signedPayload: Uint8Array, extensions?: NoiseExtensions @@ -33,11 +33,11 @@ export function createHandshakePayload( identityKey: libp2pPublicKey, identitySig: signedPayload, data: new Uint8Array(0), - extensions: extensions ?? { webtransportCerthashes: [] }, + extensions: extensions ?? { webtransportCerthashes: [] } }).subarray() } -export async function signPayload(peerId: PeerId, payload: bytes): Promise { +export async function signPayload (peerId: PeerId, payload: bytes): Promise { if (peerId.privateKey == null) { throw new Error('PrivateKey was missing from PeerId') } @@ -47,15 +47,15 @@ export async function signPayload(peerId: PeerId, payload: bytes): Promise { +export async function getPeerIdFromPayload (payload: NoiseHandshakePayload): Promise { return await peerIdFromKeys(payload.identityKey) } -export function decodePayload(payload: bytes | Uint8Array): NoiseHandshakePayload { +export function decodePayload (payload: bytes | Uint8Array): NoiseHandshakePayload { return NoiseHandshakePayload.decode(payload) } -export function getHandshakePayload(publicKey: bytes): bytes { +export function getHandshakePayload (publicKey: bytes): bytes { const prefix = uint8ArrayFromString('noise-libp2p-static-key:') return uint8ArrayConcat([prefix, publicKey], prefix.length + publicKey.length) } @@ -68,7 +68,7 @@ export function getHandshakePayload(publicKey: bytes): bytes { * @param {PeerId} remotePeer - owner's libp2p peer ID * @returns {Promise} - peer ID of payload owner */ -export async function verifySignedPayload( +export async function verifySignedPayload ( noiseStaticKey: bytes, payload: NoiseHandshakePayload, remotePeer: PeerId @@ -99,7 +99,7 @@ export async function verifySignedPayload( return payloadPeerId } -export function isValidPublicKey(pk: bytes): boolean { +export function isValidPublicKey (pk: bytes): boolean { if (!(pk instanceof Uint8Array)) { return false } diff --git a/test/interop.ts b/test/interop.ts index e096192..6751004 100644 --- a/test/interop.ts +++ b/test/interop.ts @@ -16,7 +16,7 @@ import type { PeerId } from '@libp2p/interface-peer-id' import { peerIdFromKeys } from '@libp2p/peer-id' import { Noise } from '../src/index.js' -async function createGoPeer(options: SpawnOptions): Promise { +async function createGoPeer (options: SpawnOptions): Promise { const controlPort = Math.floor(Math.random() * (50000 - 10000 + 1)) + 10000 const apiAddr = multiaddr(`/ip4/0.0.0.0/tcp/${controlPort}`) @@ -62,7 +62,7 @@ async function createGoPeer(options: SpawnOptions): Promise { } } -async function createJsPeer(options: SpawnOptions): Promise { +async function createJsPeer (options: SpawnOptions): Promise { let peerId: PeerId | undefined if (options.key != null) { @@ -94,9 +94,9 @@ async function createJsPeer(options: SpawnOptions): Promise { } } -async function main(): Promise { +async function main (): Promise { const factory: DaemonFactory = { - async spawn(options: SpawnOptions) { + async spawn (options: SpawnOptions) { if (options.type === 'go') { return await createGoPeer(options) } diff --git a/test/noise.spec.ts b/test/noise.spec.ts index c0c1619..e7d063b 100644 --- a/test/noise.spec.ts +++ b/test/noise.spec.ts @@ -177,9 +177,9 @@ describe('Noise', () => { noiseResp.secureInbound(remotePeer, inboundConnection) ]) - // @ts-ignore https://github.com/libp2p/js-libp2p-interfaces/issues/291 + // @ts-expect-error https://github.com/libp2p/js-libp2p-interfaces/issues/291 assert(uint8ArrayEquals(inbound.remoteExtensions.webtransportCerthashes[0], certhashInit)) - // @ts-ignore https://github.com/libp2p/js-libp2p-interfaces/issues/291 + // @ts-expect-error https://github.com/libp2p/js-libp2p-interfaces/issues/291 assert(uint8ArrayEquals(outbound.remoteExtensions.webtransportCerthashes[0], certhashResp)) } catch (e) { const err = e as Error From 25c7fe489b062c29dda47074b74655744fbfca46 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Mon, 3 Oct 2022 17:14:47 -0700 Subject: [PATCH 3/6] Add workaround for proto2 issue --- src/proto/payload.proto | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/proto/payload.proto b/src/proto/payload.proto index 1a55cd7..1d2740a 100644 --- a/src/proto/payload.proto +++ b/src/proto/payload.proto @@ -1,11 +1,11 @@ -syntax = "proto2"; +syntax = "proto3"; message NoiseExtensions { repeated bytes webtransport_certhashes = 1; } message NoiseHandshakePayload { - optional bytes identity_key = 1; - optional bytes identity_sig = 2; - optional NoiseExtensions extensions = 4; + bytes identity_key = 1; + bytes identity_sig = 2; + NoiseExtensions extensions = 4; } \ No newline at end of file From a586878d672f69e9ef172a7a56385e63049f1c48 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Tue, 4 Oct 2022 10:00:29 -0700 Subject: [PATCH 4/6] Use new interface defs --- package.json | 6 +++--- src/@types/libp2p.ts | 3 ++- src/noise.ts | 6 ++---- src/utils.ts | 1 - test/interop.ts | 1 + test/noise.spec.ts | 6 ++---- 6 files changed, 10 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index e470c38..628a78d 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ }, "dependencies": { "@libp2p/crypto": "^1.0.0", - "@libp2p/interface-connection-encrypter": "^2.0.1", + "@libp2p/interface-connection-encrypter": "^3.0.0", "@libp2p/interface-keys": "^1.0.2", "@libp2p/interface-peer-id": "^1.0.2", "@libp2p/logger": "^2.0.0", @@ -89,7 +89,7 @@ "devDependencies": { "@libp2p/daemon-client": "^3.0.1", "@libp2p/daemon-server": "^3.0.0", - "@libp2p/interface-connection-encrypter-compliance-tests": "^2.0.1", + "@libp2p/interface-connection-encrypter-compliance-tests": "^2.0.3", "@libp2p/interop": "^3.0.1", "@libp2p/mplex": "^5.0.0", "@libp2p/peer-id-factory": "^1.0.8", @@ -100,7 +100,7 @@ "execa": "^6.1.0", "go-libp2p": "^0.0.6", "iso-random-stream": "^2.0.2", - "libp2p": "0.39.2", + "libp2p": "0.39.4", "mkdirp": "^1.0.4", "p-defer": "^4.0.0", "protons": "^5.1.0", diff --git a/src/@types/libp2p.ts b/src/@types/libp2p.ts index e062802..16596cf 100644 --- a/src/@types/libp2p.ts +++ b/src/@types/libp2p.ts @@ -1,4 +1,5 @@ import type { ConnectionEncrypter } from '@libp2p/interface-connection-encrypter' +import type { NoiseExtensions } from '../proto/payload.js' import type { bytes32 } from './basic.js' export interface KeyPair { @@ -6,4 +7,4 @@ export interface KeyPair { privateKey: bytes32 } -export interface INoiseConnection extends ConnectionEncrypter {} +export interface INoiseConnection extends ConnectionEncrypter {} diff --git a/src/noise.ts b/src/noise.ts index a67662d..e22bb39 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -57,7 +57,7 @@ export class Noise implements INoiseConnection { * @param {PeerId} remotePeer - PeerId of the remote peer. Used to validate the integrity of the remote peer. * @returns {Promise} */ - public async secureOutbound (localPeer: PeerId, connection: Duplex, remotePeer?: PeerId): Promise { + public async secureOutbound (localPeer: PeerId, connection: Duplex, remotePeer?: PeerId): Promise> { const wrappedConnection = pbStream( connection, { @@ -76,7 +76,6 @@ export class Noise implements INoiseConnection { return { conn, - // @ts-expect-error this isn't part of the interface yet. Fix me when https://github.com/libp2p/js-libp2p-interfaces/issues/291 is fixed remoteExtensions: handshake.remoteExtensions, remotePeer: handshake.remotePeer } @@ -90,7 +89,7 @@ export class Noise implements INoiseConnection { * @param {PeerId} remotePeer - optional PeerId of the initiating peer, if known. This may only exist during transport upgrades. * @returns {Promise} */ - public async secureInbound (localPeer: PeerId, connection: Duplex, remotePeer?: PeerId): Promise { + public async secureInbound (localPeer: PeerId, connection: Duplex, remotePeer?: PeerId): Promise> { const wrappedConnection = pbStream( connection, { @@ -110,7 +109,6 @@ export class Noise implements INoiseConnection { return { conn, remotePeer: handshake.remotePeer, - // @ts-expect-error this isn't part of the interface yet. Fix me when https://github.com/libp2p/js-libp2p-interfaces/issues/291 is fixed remoteExtensions: handshake.remoteExtensions } } diff --git a/src/utils.ts b/src/utils.ts index 7e8b8d2..6eadfce 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -32,7 +32,6 @@ export function createHandshakePayload ( return NoiseHandshakePayload.encode({ identityKey: libp2pPublicKey, identitySig: signedPayload, - data: new Uint8Array(0), extensions: extensions ?? { webtransportCerthashes: [] } }).subarray() } diff --git a/test/interop.ts b/test/interop.ts index 6751004..ff3a4d0 100644 --- a/test/interop.ts +++ b/test/interop.ts @@ -78,6 +78,7 @@ async function createJsPeer (options: SpawnOptions): Promise { }, transports: [new TCP()], streamMuxers: [new Mplex()], + // @ts-expect-error libp2p options is still referencing the old connection encrypter interface https://github.com/libp2p/js-libp2p/pull/1402 connectionEncryption: [new Noise()] } diff --git a/test/noise.spec.ts b/test/noise.spec.ts index e7d063b..5a78ebc 100644 --- a/test/noise.spec.ts +++ b/test/noise.spec.ts @@ -177,10 +177,8 @@ describe('Noise', () => { noiseResp.secureInbound(remotePeer, inboundConnection) ]) - // @ts-expect-error https://github.com/libp2p/js-libp2p-interfaces/issues/291 - assert(uint8ArrayEquals(inbound.remoteExtensions.webtransportCerthashes[0], certhashInit)) - // @ts-expect-error https://github.com/libp2p/js-libp2p-interfaces/issues/291 - assert(uint8ArrayEquals(outbound.remoteExtensions.webtransportCerthashes[0], certhashResp)) + assert(uint8ArrayEquals(inbound.remoteExtensions?.webtransportCerthashes[0] || new Uint8Array(), certhashInit)) + assert(uint8ArrayEquals(outbound.remoteExtensions?.webtransportCerthashes[0] || new Uint8Array(), certhashResp)) } catch (e) { const err = e as Error assert(false, err.message) From bc4d5093edabe09b150c785ff7097c03a63116f9 Mon Sep 17 00:00:00 2001 From: Marin Petrunic Date: Wed, 5 Oct 2022 10:50:10 +0200 Subject: [PATCH 5/6] chore: fix lint --- package.json | 2 +- test/noise.spec.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 628a78d..f99773f 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ }, "devDependencies": { "@libp2p/daemon-client": "^3.0.1", - "@libp2p/daemon-server": "^3.0.0", + "@libp2p/daemon-server": "^3.0.1", "@libp2p/interface-connection-encrypter-compliance-tests": "^2.0.3", "@libp2p/interop": "^3.0.1", "@libp2p/mplex": "^5.0.0", diff --git a/test/noise.spec.ts b/test/noise.spec.ts index 5a78ebc..77cfd43 100644 --- a/test/noise.spec.ts +++ b/test/noise.spec.ts @@ -177,8 +177,8 @@ describe('Noise', () => { noiseResp.secureInbound(remotePeer, inboundConnection) ]) - assert(uint8ArrayEquals(inbound.remoteExtensions?.webtransportCerthashes[0] || new Uint8Array(), certhashInit)) - assert(uint8ArrayEquals(outbound.remoteExtensions?.webtransportCerthashes[0] || new Uint8Array(), certhashResp)) + assert(uint8ArrayEquals(inbound.remoteExtensions?.webtransportCerthashes[0] ?? new Uint8Array(), certhashInit)) + assert(uint8ArrayEquals(outbound.remoteExtensions?.webtransportCerthashes[0] ?? new Uint8Array(), certhashResp)) } catch (e) { const err = e as Error assert(false, err.message) From 06f5673e398bbf8307d11de73b32fe631545aadd Mon Sep 17 00:00:00 2001 From: Marin Petrunic Date: Wed, 5 Oct 2022 10:54:56 +0200 Subject: [PATCH 6/6] fix: proto file to pass interop tests --- src/proto/payload.proto | 2 +- src/proto/payload.ts | 11 ++--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/proto/payload.proto b/src/proto/payload.proto index 1d2740a..cdb2383 100644 --- a/src/proto/payload.proto +++ b/src/proto/payload.proto @@ -7,5 +7,5 @@ message NoiseExtensions { message NoiseHandshakePayload { bytes identity_key = 1; bytes identity_sig = 2; - NoiseExtensions extensions = 4; + optional NoiseExtensions extensions = 4; } \ No newline at end of file diff --git a/src/proto/payload.ts b/src/proto/payload.ts index b662af1..3a28281 100644 --- a/src/proto/payload.ts +++ b/src/proto/payload.ts @@ -70,7 +70,7 @@ export namespace NoiseExtensions { export interface NoiseHandshakePayload { identityKey: Uint8Array identitySig: Uint8Array - extensions: NoiseExtensions + extensions?: NoiseExtensions } export namespace NoiseHandshakePayload { @@ -100,8 +100,6 @@ export namespace NoiseHandshakePayload { if (obj.extensions != null) { writer.uint32(34) NoiseExtensions.codec().encode(obj.extensions, writer) - } else { - throw new Error('Protocol error: required field "extensions" was not found in object') } if (opts.lengthDelimited !== false) { @@ -110,8 +108,7 @@ export namespace NoiseHandshakePayload { }, (reader, length) => { const obj: any = { identityKey: new Uint8Array(0), - identitySig: new Uint8Array(0), - extensions: undefined + identitySig: new Uint8Array(0) } const end = length == null ? reader.len : reader.pos + length @@ -143,10 +140,6 @@ export namespace NoiseHandshakePayload { throw new Error('Protocol error: value for required field "identitySig" was not found in protobuf') } - if (obj.extensions == null) { - throw new Error('Protocol error: value for required field "extensions" was not found in protobuf') - } - return obj }) }