From b5ced70ce7b5f9d1226536004d92d64f34acca1f Mon Sep 17 00:00:00 2001 From: chad Date: Thu, 7 Dec 2023 08:58:54 -0500 Subject: [PATCH 1/6] wip --- packages/transport-webrtc/package.json | 3 +- packages/transport-webrtc/src/error.ts | 8 ++--- .../src/private-to-public/sdp.ts | 29 +++++++++---------- .../src/private-to-public/transport.ts | 10 +++---- packages/transport-webrtc/test/sdp.spec.ts | 4 +-- 5 files changed, 24 insertions(+), 30 deletions(-) diff --git a/packages/transport-webrtc/package.json b/packages/transport-webrtc/package.json index 1e2a21998e..a349a03d5a 100644 --- a/packages/transport-webrtc/package.json +++ b/packages/transport-webrtc/package.json @@ -39,7 +39,7 @@ "scripts": { "generate": "protons src/private-to-private/pb/message.proto src/pb/message.proto", "build": "aegir build", - "test": "aegir test -t node -t browser -- --exit", + "test": "aegir test -t node -t browser -- --exit -g \"converts multiaddr with certhash to an answer SDP\"", "test:node": "aegir test -t node --cov -- --exit", "test:chrome": "aegir test -t browser --cov", "test:firefox": "aegir test -t browser -- --browser firefox", @@ -67,7 +67,6 @@ "it-stream-types": "^2.0.1", "it-to-buffer": "^4.0.2", "multiformats": "^12.1.3", - "multihashes": "^4.0.3", "node-datachannel": "^0.5.0-dev", "p-defer": "^4.0.0", "p-event": "^6.0.0", diff --git a/packages/transport-webrtc/src/error.ts b/packages/transport-webrtc/src/error.ts index c501f0ee0b..cde25484aa 100644 --- a/packages/transport-webrtc/src/error.ts +++ b/packages/transport-webrtc/src/error.ts @@ -111,12 +111,12 @@ export function unimplemented (methodName: string): UnimplementedError { } export class UnsupportedHashAlgorithmError extends WebRTCTransportError { - constructor (algo: string) { - super(`unsupported hash algorithm: ${algo}`, codes.ERR_HASH_NOT_SUPPORTED) + constructor (algo: number) { + super(`unsupported hash algorithm code: ${algo} please see the codes at https://github.com/multiformats/multicodec/blob/master/table.csv `, codes.ERR_HASH_NOT_SUPPORTED) this.name = 'WebRTC/UnsupportedHashAlgorithmError' } } -export function unsupportedHashAlgorithm (algorithm: string): UnsupportedHashAlgorithmError { - return new UnsupportedHashAlgorithmError(algorithm) +export function unsupportedHashAlgorithmCode (code: number): UnsupportedHashAlgorithmError { + return new UnsupportedHashAlgorithmError(code) } diff --git a/packages/transport-webrtc/src/private-to-public/sdp.ts b/packages/transport-webrtc/src/private-to-public/sdp.ts index 0520a820cf..1fb34ef5bc 100644 --- a/packages/transport-webrtc/src/private-to-public/sdp.ts +++ b/packages/transport-webrtc/src/private-to-public/sdp.ts @@ -1,11 +1,9 @@ -import { bases } from 'multiformats/basics' -import * as multihashes from 'multihashes' -import { inappropriateMultiaddr, invalidArgument, invalidFingerprint, unsupportedHashAlgorithm } from '../error.js' +import { type Multiaddr } from '@multiformats/multiaddr' +import { bases, digest } from 'multiformats/basics' +import { inappropriateMultiaddr, invalidArgument, invalidFingerprint, unsupportedHashAlgorithmCode } from '../error.js' import { CERTHASH_CODE } from './transport.js' import type { LoggerOptions } from '@libp2p/interface' -import type { Multiaddr } from '@multiformats/multiaddr' -import type { HashCode, HashName } from 'multihashes' - +import type { MultihashDigest } from 'multiformats/hashes/interface' /** * Get base2 | identity decoders */ @@ -71,9 +69,8 @@ export function certhash (ma: Multiaddr): string { /** * Convert a certhash into a multihash */ -export function decodeCerthash (certhash: string): { code: HashCode, name: HashName, length: number, digest: Uint8Array } { - const mbdecoded = mbdecoder.decode(certhash) - return multihashes.decode(mbdecoded) +export function decodeCerthash (certhash: string): MultihashDigest { + return digest.decode(mbdecoder.decode(certhash)) } /** @@ -81,7 +78,7 @@ export function decodeCerthash (certhash: string): { code: HashCode, name: HashN */ export function ma2Fingerprint (ma: Multiaddr): string[] { const mhdecoded = decodeCerthash(certhash(ma)) - const prefix = toSupportedHashFunction(mhdecoded.name) + const prefix = toSupportedHashFunction(mhdecoded.code) const fingerprint = mhdecoded.digest.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '') const sdp = fingerprint.match(/.{1,2}/g) @@ -95,16 +92,16 @@ export function ma2Fingerprint (ma: Multiaddr): string[] { /** * Normalize the hash name from a given multihash has name */ -export function toSupportedHashFunction (name: multihashes.HashName): string { - switch (name) { - case 'sha1': +export function toSupportedHashFunction (code: number): string { + switch (code) { + case 0x11: return 'sha-1' - case 'sha2-256': + case 0x16: return 'sha-256' - case 'sha2-512': + case 0x14: return 'sha-512' default: - throw unsupportedHashAlgorithm(name) + throw unsupportedHashAlgorithmCode(code) } } diff --git a/packages/transport-webrtc/src/private-to-public/transport.ts b/packages/transport-webrtc/src/private-to-public/transport.ts index ddfab7e878..bf970a9723 100644 --- a/packages/transport-webrtc/src/private-to-public/transport.ts +++ b/packages/transport-webrtc/src/private-to-public/transport.ts @@ -3,7 +3,6 @@ import { type CreateListenerOptions, transportSymbol, type Transport, type Liste import * as p from '@libp2p/peer-id' import { protocols } from '@multiformats/multiaddr' import { WebRTCDirect } from '@multiformats/multiaddr-matcher' -import * as multihashes from 'multihashes' import { concat } from 'uint8arrays/concat' import { fromString as uint8arrayFromString } from 'uint8arrays/from-string' import { dataChannelError, inappropriateMultiaddr, unimplemented, invalidArgument } from '../error.js' @@ -128,7 +127,7 @@ export class WebRTCDirectTransport implements Transport { const certificate = await RTCPeerConnection.generateCertificate({ name: 'ECDSA', namedCurve: 'P-256', - hash: sdp.toSupportedHashFunction(remoteCerthash.name) + hash: remoteCerthash } as any) const peerConnection = new RTCPeerConnection({ certificates: [certificate] }) @@ -185,7 +184,7 @@ export class WebRTCDirectTransport implements Transport { // Do noise handshake. // Set the Noise Prologue to libp2p-webrtc-noise: before starting the actual Noise handshake. // is the concatenation of the of the two TLS fingerprints of A and B in their multihash byte representation, sorted in ascending order. - const fingerprintsPrologue = this.generateNoisePrologue(peerConnection, remoteCerthash.code, ma) + const fingerprintsPrologue = this.generateNoisePrologue(peerConnection, ma) // Since we use the default crypto interface and do not use a static key or early data, // we pass in undefined for these parameters. @@ -263,7 +262,7 @@ export class WebRTCDirectTransport implements Transport { * Generate a noise prologue from the peer connection's certificate. * noise prologue = bytes('libp2p-webrtc-noise:') + noise-responder fingerprint + noise-initiator fingerprint */ - private generateNoisePrologue (pc: RTCPeerConnection, hashCode: multihashes.HashCode, ma: Multiaddr): Uint8Array { + private generateNoisePrologue (pc: RTCPeerConnection, ma: Multiaddr): Uint8Array { if (pc.getConfiguration().certificates?.length === 0) { throw invalidArgument('no local certificate') } @@ -277,10 +276,9 @@ export class WebRTCDirectTransport implements Transport { const localFpString = localFingerprint.trim().toLowerCase().replaceAll(':', '') const localFpArray = uint8arrayFromString(localFpString, 'hex') - const local = multihashes.encode(localFpArray, hashCode) const remote: Uint8Array = sdp.mbdecoder.decode(sdp.certhash(ma)) const prefix = uint8arrayFromString('libp2p-webrtc-noise:') - return concat([prefix, local, remote]) + return concat([prefix, localFpArray, remote]) } } diff --git a/packages/transport-webrtc/test/sdp.spec.ts b/packages/transport-webrtc/test/sdp.spec.ts index cff8a794b1..60d0d4dc8c 100644 --- a/packages/transport-webrtc/test/sdp.spec.ts +++ b/packages/transport-webrtc/test/sdp.spec.ts @@ -39,9 +39,9 @@ describe('SDP', () => { // sha2-256 multihash 0x12 permanent // https://github.com/multiformats/multicodec/blob/master/table.csv - expect(decoded.name).to.equal('sha2-256') + expect(decoded).to.equal('sha2-256') expect(decoded.code).to.equal(0x12) - expect(decoded.length).to.equal(32) + expect(decoded.size).to.equal(32) expect(decoded.digest.toString()).to.equal('114,104,71,205,72,176,94,197,96,77,21,156,191,64,29,111,0,161,35,236,144,23,14,44,209,179,143,210,157,55,229,177') }) From b906dc2b31bb031f2338e0ee72669172dfb55f2d Mon Sep 17 00:00:00 2001 From: chad Date: Thu, 2 May 2024 18:21:41 -0500 Subject: [PATCH 2/6] fix: remove deprecated lib multihashes (#2264) --- packages/transport-webrtc/package.json | 4 +- packages/transport-webrtc/src/error.ts | 38 +++++++++---------- .../src/private-to-public/sdp.ts | 26 ++++++------- packages/transport-webrtc/test/sdp.spec.ts | 1 - 4 files changed, 34 insertions(+), 35 deletions(-) diff --git a/packages/transport-webrtc/package.json b/packages/transport-webrtc/package.json index cca6f15a1c..2db6a8ca96 100644 --- a/packages/transport-webrtc/package.json +++ b/packages/transport-webrtc/package.json @@ -39,8 +39,8 @@ "scripts": { "generate": "protons src/private-to-private/pb/message.proto src/pb/message.proto", "build": "aegir build", - "test": "aegir test -t node -t browser -- --exit -g \"converts multiaddr with certhash to an answer SDP\"", - "test:node": "aegir test -t node --cov -- --exit", + "test": "aegir test -t node -t browser", + "test:node": "aegir test -t node --cov", "test:chrome": "aegir test -t browser --cov", "test:firefox": "aegir test -t browser -- --browser firefox", "lint": "aegir lint", diff --git a/packages/transport-webrtc/src/error.ts b/packages/transport-webrtc/src/error.ts index cde25484aa..0254d63df0 100644 --- a/packages/transport-webrtc/src/error.ts +++ b/packages/transport-webrtc/src/error.ts @@ -15,108 +15,108 @@ export enum codes { } export class WebRTCTransportError extends CodeError { - constructor (msg: string, code?: string) { + constructor(msg: string, code?: string) { super(`WebRTC transport error: ${msg}`, code ?? '') this.name = 'WebRTCTransportError' } } export class ConnectionClosedError extends WebRTCTransportError { - constructor (state: RTCPeerConnectionState, msg: string) { + constructor(state: RTCPeerConnectionState, msg: string) { super(`peerconnection moved to state: ${state}: ${msg}`, codes.ERR_CONNECTION_CLOSED) this.name = 'WebRTC/ConnectionClosed' } } -export function connectionClosedError (state: RTCPeerConnectionState, msg: string): ConnectionClosedError { +export function connectionClosedError(state: RTCPeerConnectionState, msg: string): ConnectionClosedError { return new ConnectionClosedError(state, msg) } export class DataChannelError extends WebRTCTransportError { - constructor (streamLabel: string, msg: string) { + constructor(streamLabel: string, msg: string) { super(`[stream: ${streamLabel}] data channel error: ${msg}`, codes.ERR_DATA_CHANNEL) this.name = 'WebRTC/DataChannelError' } } -export function dataChannelError (streamLabel: string, msg: string): DataChannelError { +export function dataChannelError(streamLabel: string, msg: string): DataChannelError { return new DataChannelError(streamLabel, msg) } export class InappropriateMultiaddrError extends WebRTCTransportError { - constructor (msg: string) { + constructor(msg: string) { super(`There was a problem with the Multiaddr which was passed in: ${msg}`, codes.ERR_INVALID_MULTIADDR) this.name = 'WebRTC/InappropriateMultiaddrError' } } -export function inappropriateMultiaddr (msg: string): InappropriateMultiaddrError { +export function inappropriateMultiaddr(msg: string): InappropriateMultiaddrError { return new InappropriateMultiaddrError(msg) } export class InvalidArgumentError extends WebRTCTransportError { - constructor (msg: string) { + constructor(msg: string) { super(`There was a problem with a provided argument: ${msg}`, codes.ERR_INVALID_PARAMETERS) this.name = 'WebRTC/InvalidArgumentError' } } -export function invalidArgument (msg: string): InvalidArgumentError { +export function invalidArgument(msg: string): InvalidArgumentError { return new InvalidArgumentError(msg) } export class InvalidFingerprintError extends WebRTCTransportError { - constructor (fingerprint: string, source: string) { + constructor(fingerprint: string, source: string) { super(`Invalid fingerprint "${fingerprint}" within ${source}`, codes.ERR_INVALID_FINGERPRINT) this.name = 'WebRTC/InvalidFingerprintError' } } -export function invalidFingerprint (fingerprint: string, source: string): InvalidFingerprintError { +export function invalidFingerprint(fingerprint: string, source: string): InvalidFingerprintError { return new InvalidFingerprintError(fingerprint, source) } export class OperationAbortedError extends WebRTCTransportError { - constructor (context: string, abortReason: string) { + constructor(context: string, abortReason: string) { super(`Signalled to abort because (${abortReason}}) ${context}`, codes.ERR_ALREADY_ABORTED) this.name = 'WebRTC/OperationAbortedError' } } -export function operationAborted (context: string, reason: string): OperationAbortedError { +export function operationAborted(context: string, reason: string): OperationAbortedError { return new OperationAbortedError(context, reason) } export class OverStreamLimitError extends WebRTCTransportError { - constructor (msg: string) { + constructor(msg: string) { const code = msg.startsWith('inbound') ? codes.ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS : codes.ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS super(msg, code) this.name = 'WebRTC/OverStreamLimitError' } } -export function overStreamLimit (dir: Direction, proto: string): OverStreamLimitError { +export function overStreamLimit(dir: Direction, proto: string): OverStreamLimitError { return new OverStreamLimitError(`${dir} stream limit reached for protocol - ${proto}`) } export class UnimplementedError extends WebRTCTransportError { - constructor (methodName: string) { + constructor(methodName: string) { super(`A method (${methodName}) was called though it has been intentionally left unimplemented.`, codes.ERR_NOT_IMPLEMENTED) this.name = 'WebRTC/UnimplementedError' } } -export function unimplemented (methodName: string): UnimplementedError { +export function unimplemented(methodName: string): UnimplementedError { return new UnimplementedError(methodName) } export class UnsupportedHashAlgorithmError extends WebRTCTransportError { - constructor (algo: number) { + constructor(algo: number) { super(`unsupported hash algorithm code: ${algo} please see the codes at https://github.com/multiformats/multicodec/blob/master/table.csv `, codes.ERR_HASH_NOT_SUPPORTED) this.name = 'WebRTC/UnsupportedHashAlgorithmError' } } -export function unsupportedHashAlgorithmCode (code: number): UnsupportedHashAlgorithmError { +export function unsupportedHashAlgorithmCode(code: number): UnsupportedHashAlgorithmError { return new UnsupportedHashAlgorithmError(code) } diff --git a/packages/transport-webrtc/src/private-to-public/sdp.ts b/packages/transport-webrtc/src/private-to-public/sdp.ts index 1fb34ef5bc..dcc30a8e08 100644 --- a/packages/transport-webrtc/src/private-to-public/sdp.ts +++ b/packages/transport-webrtc/src/private-to-public/sdp.ts @@ -10,7 +10,7 @@ import type { MultihashDigest } from 'multiformats/hashes/interface' // @ts-expect-error - Not easy to combine these types. export const mbdecoder: any = Object.values(bases).map(b => b.decoder).reduce((d, b) => d.or(b)) -export function getLocalFingerprint (pc: RTCPeerConnection, options: LoggerOptions): string | undefined { +export function getLocalFingerprint(pc: RTCPeerConnection, options: LoggerOptions): string | undefined { // try to fetch fingerprint from local certificate const localCert = pc.getConfiguration().certificates?.at(0) if (localCert == null || localCert.getFingerprints == null) { @@ -37,14 +37,14 @@ export function getLocalFingerprint (pc: RTCPeerConnection, options: LoggerOptio } const fingerprintRegex = /^a=fingerprint:(?:\w+-[0-9]+)\s(?(:?[0-9a-fA-F]{2})+)$/m -export function getFingerprintFromSdp (sdp: string): string | undefined { +export function getFingerprintFromSdp(sdp: string): string | undefined { const searchResult = sdp.match(fingerprintRegex) return searchResult?.groups?.fingerprint } /** * Get base2 | identity decoders */ -function ipv (ma: Multiaddr): string { +function ipv(ma: Multiaddr): string { for (const proto of ma.protoNames()) { if (proto.startsWith('ip')) { return proto.toUpperCase() @@ -55,7 +55,7 @@ function ipv (ma: Multiaddr): string { } // Extract the certhash from a multiaddr -export function certhash (ma: Multiaddr): string { +export function certhash(ma: Multiaddr): string { const tups = ma.stringTuples() const certhash = tups.filter((tup) => tup[0] === CERTHASH_CODE).map((tup) => tup[1])[0] @@ -69,14 +69,14 @@ export function certhash (ma: Multiaddr): string { /** * Convert a certhash into a multihash */ -export function decodeCerthash (certhash: string): MultihashDigest { +export function decodeCerthash(certhash: string): MultihashDigest { return digest.decode(mbdecoder.decode(certhash)) } /** * Extract the fingerprint from a multiaddr */ -export function ma2Fingerprint (ma: Multiaddr): string[] { +export function ma2Fingerprint(ma: Multiaddr): string[] { const mhdecoded = decodeCerthash(certhash(ma)) const prefix = toSupportedHashFunction(mhdecoded.code) const fingerprint = mhdecoded.digest.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '') @@ -92,13 +92,13 @@ export function ma2Fingerprint (ma: Multiaddr): string[] { /** * Normalize the hash name from a given multihash has name */ -export function toSupportedHashFunction (code: number): string { +export function toSupportedHashFunction(code: number): string { switch (code) { case 0x11: - return 'sha-1' - case 0x16: + return 'sha1' + case 0x12: return 'sha-256' - case 0x14: + case 0x13: return 'sha-512' default: throw unsupportedHashAlgorithmCode(code) @@ -108,7 +108,7 @@ export function toSupportedHashFunction (code: number): string { /** * Convert a multiaddr into a SDP */ -function ma2sdp (ma: Multiaddr, ufrag: string): string { +function ma2sdp(ma: Multiaddr, ufrag: string): string { const { host, port } = ma.toOptions() const ipVersion = ipv(ma) const [CERTFP] = ma2Fingerprint(ma) @@ -133,7 +133,7 @@ a=candidate:1467250027 1 UDP 1467250027 ${host} ${port} typ host\r\n` /** * Create an answer SDP from a multiaddr */ -export function fromMultiAddr (ma: Multiaddr, ufrag: string): RTCSessionDescriptionInit { +export function fromMultiAddr(ma: Multiaddr, ufrag: string): RTCSessionDescriptionInit { return { type: 'answer', sdp: ma2sdp(ma, ufrag) @@ -143,7 +143,7 @@ export function fromMultiAddr (ma: Multiaddr, ufrag: string): RTCSessionDescript /** * Replace (munge) the ufrag and password values in a SDP */ -export function munge (desc: RTCSessionDescriptionInit, ufrag: string): RTCSessionDescriptionInit { +export function munge(desc: RTCSessionDescriptionInit, ufrag: string): RTCSessionDescriptionInit { if (desc.sdp === undefined) { throw invalidArgument("Can't munge a missing SDP") } diff --git a/packages/transport-webrtc/test/sdp.spec.ts b/packages/transport-webrtc/test/sdp.spec.ts index 60d0d4dc8c..a66c532339 100644 --- a/packages/transport-webrtc/test/sdp.spec.ts +++ b/packages/transport-webrtc/test/sdp.spec.ts @@ -39,7 +39,6 @@ describe('SDP', () => { // sha2-256 multihash 0x12 permanent // https://github.com/multiformats/multicodec/blob/master/table.csv - expect(decoded).to.equal('sha2-256') expect(decoded.code).to.equal(0x12) expect(decoded.size).to.equal(32) expect(decoded.digest.toString()).to.equal('114,104,71,205,72,176,94,197,96,77,21,156,191,64,29,111,0,161,35,236,144,23,14,44,209,179,143,210,157,55,229,177') From 2cebdc2627325f5d73c8449c47194217caa2c3a5 Mon Sep 17 00:00:00 2001 From: chad Date: Thu, 2 May 2024 18:25:39 -0500 Subject: [PATCH 3/6] chore: linting fixes --- packages/transport-webrtc/src/error.ts | 38 +++++++++---------- .../src/private-to-public/sdp.ts | 20 +++++----- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/transport-webrtc/src/error.ts b/packages/transport-webrtc/src/error.ts index 0254d63df0..cde25484aa 100644 --- a/packages/transport-webrtc/src/error.ts +++ b/packages/transport-webrtc/src/error.ts @@ -15,108 +15,108 @@ export enum codes { } export class WebRTCTransportError extends CodeError { - constructor(msg: string, code?: string) { + constructor (msg: string, code?: string) { super(`WebRTC transport error: ${msg}`, code ?? '') this.name = 'WebRTCTransportError' } } export class ConnectionClosedError extends WebRTCTransportError { - constructor(state: RTCPeerConnectionState, msg: string) { + constructor (state: RTCPeerConnectionState, msg: string) { super(`peerconnection moved to state: ${state}: ${msg}`, codes.ERR_CONNECTION_CLOSED) this.name = 'WebRTC/ConnectionClosed' } } -export function connectionClosedError(state: RTCPeerConnectionState, msg: string): ConnectionClosedError { +export function connectionClosedError (state: RTCPeerConnectionState, msg: string): ConnectionClosedError { return new ConnectionClosedError(state, msg) } export class DataChannelError extends WebRTCTransportError { - constructor(streamLabel: string, msg: string) { + constructor (streamLabel: string, msg: string) { super(`[stream: ${streamLabel}] data channel error: ${msg}`, codes.ERR_DATA_CHANNEL) this.name = 'WebRTC/DataChannelError' } } -export function dataChannelError(streamLabel: string, msg: string): DataChannelError { +export function dataChannelError (streamLabel: string, msg: string): DataChannelError { return new DataChannelError(streamLabel, msg) } export class InappropriateMultiaddrError extends WebRTCTransportError { - constructor(msg: string) { + constructor (msg: string) { super(`There was a problem with the Multiaddr which was passed in: ${msg}`, codes.ERR_INVALID_MULTIADDR) this.name = 'WebRTC/InappropriateMultiaddrError' } } -export function inappropriateMultiaddr(msg: string): InappropriateMultiaddrError { +export function inappropriateMultiaddr (msg: string): InappropriateMultiaddrError { return new InappropriateMultiaddrError(msg) } export class InvalidArgumentError extends WebRTCTransportError { - constructor(msg: string) { + constructor (msg: string) { super(`There was a problem with a provided argument: ${msg}`, codes.ERR_INVALID_PARAMETERS) this.name = 'WebRTC/InvalidArgumentError' } } -export function invalidArgument(msg: string): InvalidArgumentError { +export function invalidArgument (msg: string): InvalidArgumentError { return new InvalidArgumentError(msg) } export class InvalidFingerprintError extends WebRTCTransportError { - constructor(fingerprint: string, source: string) { + constructor (fingerprint: string, source: string) { super(`Invalid fingerprint "${fingerprint}" within ${source}`, codes.ERR_INVALID_FINGERPRINT) this.name = 'WebRTC/InvalidFingerprintError' } } -export function invalidFingerprint(fingerprint: string, source: string): InvalidFingerprintError { +export function invalidFingerprint (fingerprint: string, source: string): InvalidFingerprintError { return new InvalidFingerprintError(fingerprint, source) } export class OperationAbortedError extends WebRTCTransportError { - constructor(context: string, abortReason: string) { + constructor (context: string, abortReason: string) { super(`Signalled to abort because (${abortReason}}) ${context}`, codes.ERR_ALREADY_ABORTED) this.name = 'WebRTC/OperationAbortedError' } } -export function operationAborted(context: string, reason: string): OperationAbortedError { +export function operationAborted (context: string, reason: string): OperationAbortedError { return new OperationAbortedError(context, reason) } export class OverStreamLimitError extends WebRTCTransportError { - constructor(msg: string) { + constructor (msg: string) { const code = msg.startsWith('inbound') ? codes.ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS : codes.ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS super(msg, code) this.name = 'WebRTC/OverStreamLimitError' } } -export function overStreamLimit(dir: Direction, proto: string): OverStreamLimitError { +export function overStreamLimit (dir: Direction, proto: string): OverStreamLimitError { return new OverStreamLimitError(`${dir} stream limit reached for protocol - ${proto}`) } export class UnimplementedError extends WebRTCTransportError { - constructor(methodName: string) { + constructor (methodName: string) { super(`A method (${methodName}) was called though it has been intentionally left unimplemented.`, codes.ERR_NOT_IMPLEMENTED) this.name = 'WebRTC/UnimplementedError' } } -export function unimplemented(methodName: string): UnimplementedError { +export function unimplemented (methodName: string): UnimplementedError { return new UnimplementedError(methodName) } export class UnsupportedHashAlgorithmError extends WebRTCTransportError { - constructor(algo: number) { + constructor (algo: number) { super(`unsupported hash algorithm code: ${algo} please see the codes at https://github.com/multiformats/multicodec/blob/master/table.csv `, codes.ERR_HASH_NOT_SUPPORTED) this.name = 'WebRTC/UnsupportedHashAlgorithmError' } } -export function unsupportedHashAlgorithmCode(code: number): UnsupportedHashAlgorithmError { +export function unsupportedHashAlgorithmCode (code: number): UnsupportedHashAlgorithmError { return new UnsupportedHashAlgorithmError(code) } diff --git a/packages/transport-webrtc/src/private-to-public/sdp.ts b/packages/transport-webrtc/src/private-to-public/sdp.ts index dcc30a8e08..f5bc735fce 100644 --- a/packages/transport-webrtc/src/private-to-public/sdp.ts +++ b/packages/transport-webrtc/src/private-to-public/sdp.ts @@ -10,7 +10,7 @@ import type { MultihashDigest } from 'multiformats/hashes/interface' // @ts-expect-error - Not easy to combine these types. export const mbdecoder: any = Object.values(bases).map(b => b.decoder).reduce((d, b) => d.or(b)) -export function getLocalFingerprint(pc: RTCPeerConnection, options: LoggerOptions): string | undefined { +export function getLocalFingerprint (pc: RTCPeerConnection, options: LoggerOptions): string | undefined { // try to fetch fingerprint from local certificate const localCert = pc.getConfiguration().certificates?.at(0) if (localCert == null || localCert.getFingerprints == null) { @@ -37,14 +37,14 @@ export function getLocalFingerprint(pc: RTCPeerConnection, options: LoggerOption } const fingerprintRegex = /^a=fingerprint:(?:\w+-[0-9]+)\s(?(:?[0-9a-fA-F]{2})+)$/m -export function getFingerprintFromSdp(sdp: string): string | undefined { +export function getFingerprintFromSdp (sdp: string): string | undefined { const searchResult = sdp.match(fingerprintRegex) return searchResult?.groups?.fingerprint } /** * Get base2 | identity decoders */ -function ipv(ma: Multiaddr): string { +function ipv (ma: Multiaddr): string { for (const proto of ma.protoNames()) { if (proto.startsWith('ip')) { return proto.toUpperCase() @@ -55,7 +55,7 @@ function ipv(ma: Multiaddr): string { } // Extract the certhash from a multiaddr -export function certhash(ma: Multiaddr): string { +export function certhash (ma: Multiaddr): string { const tups = ma.stringTuples() const certhash = tups.filter((tup) => tup[0] === CERTHASH_CODE).map((tup) => tup[1])[0] @@ -69,14 +69,14 @@ export function certhash(ma: Multiaddr): string { /** * Convert a certhash into a multihash */ -export function decodeCerthash(certhash: string): MultihashDigest { +export function decodeCerthash (certhash: string): MultihashDigest { return digest.decode(mbdecoder.decode(certhash)) } /** * Extract the fingerprint from a multiaddr */ -export function ma2Fingerprint(ma: Multiaddr): string[] { +export function ma2Fingerprint (ma: Multiaddr): string[] { const mhdecoded = decodeCerthash(certhash(ma)) const prefix = toSupportedHashFunction(mhdecoded.code) const fingerprint = mhdecoded.digest.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '') @@ -92,7 +92,7 @@ export function ma2Fingerprint(ma: Multiaddr): string[] { /** * Normalize the hash name from a given multihash has name */ -export function toSupportedHashFunction(code: number): string { +export function toSupportedHashFunction (code: number): string { switch (code) { case 0x11: return 'sha1' @@ -108,7 +108,7 @@ export function toSupportedHashFunction(code: number): string { /** * Convert a multiaddr into a SDP */ -function ma2sdp(ma: Multiaddr, ufrag: string): string { +function ma2sdp (ma: Multiaddr, ufrag: string): string { const { host, port } = ma.toOptions() const ipVersion = ipv(ma) const [CERTFP] = ma2Fingerprint(ma) @@ -133,7 +133,7 @@ a=candidate:1467250027 1 UDP 1467250027 ${host} ${port} typ host\r\n` /** * Create an answer SDP from a multiaddr */ -export function fromMultiAddr(ma: Multiaddr, ufrag: string): RTCSessionDescriptionInit { +export function fromMultiAddr (ma: Multiaddr, ufrag: string): RTCSessionDescriptionInit { return { type: 'answer', sdp: ma2sdp(ma, ufrag) @@ -143,7 +143,7 @@ export function fromMultiAddr(ma: Multiaddr, ufrag: string): RTCSessionDescripti /** * Replace (munge) the ufrag and password values in a SDP */ -export function munge(desc: RTCSessionDescriptionInit, ufrag: string): RTCSessionDescriptionInit { +export function munge (desc: RTCSessionDescriptionInit, ufrag: string): RTCSessionDescriptionInit { if (desc.sdp === undefined) { throw invalidArgument("Can't munge a missing SDP") } From 511e941b9adc887116126759a9318aaef67f7cd6 Mon Sep 17 00:00:00 2001 From: chad Date: Thu, 2 May 2024 19:20:50 -0500 Subject: [PATCH 4/6] fix: update hash encoding --- .../src/private-to-public/transport.ts | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/transport-webrtc/src/private-to-public/transport.ts b/packages/transport-webrtc/src/private-to-public/transport.ts index 298e821480..8136b1d9b9 100644 --- a/packages/transport-webrtc/src/private-to-public/transport.ts +++ b/packages/transport-webrtc/src/private-to-public/transport.ts @@ -3,9 +3,11 @@ import { type CreateListenerOptions, transportSymbol, type Transport, type Liste import * as p from '@libp2p/peer-id' import { protocols } from '@multiformats/multiaddr' import { WebRTCDirect } from '@multiformats/multiaddr-matcher' +import { sha1 } from 'multiformats/hashes/sha1' +import { sha256, sha512 } from 'multiformats/hashes/sha2' import { concat } from 'uint8arrays/concat' import { fromString as uint8arrayFromString } from 'uint8arrays/from-string' -import { dataChannelError, inappropriateMultiaddr, unimplemented, invalidArgument } from '../error.js' +import { dataChannelError, inappropriateMultiaddr, unimplemented, invalidArgument, unsupportedHashAlgorithmCode } from '../error.js' import { WebRTCMultiaddrConnection } from '../maconn.js' import { DataChannelMuxerFactory } from '../muxer.js' import { createStream } from '../stream.js' @@ -134,7 +136,7 @@ export class WebRTCDirectTransport implements Transport { const certificate = await RTCPeerConnection.generateCertificate({ name: 'ECDSA', namedCurve: 'P-256', - hash: remoteCerthash + hash: sdp.toSupportedHashFunction(remoteCerthash.code) } as any) const peerConnection = new RTCPeerConnection({ certificates: [certificate] }) @@ -191,7 +193,7 @@ export class WebRTCDirectTransport implements Transport { // Do noise handshake. // Set the Noise Prologue to libp2p-webrtc-noise: before starting the actual Noise handshake. // is the concatenation of the of the two TLS fingerprints of A and B in their multihash byte representation, sorted in ascending order. - const fingerprintsPrologue = this.generateNoisePrologue(peerConnection, ma) + const fingerprintsPrologue = await this.generateNoisePrologue(peerConnection, remoteCerthash.code, ma) // Since we use the default crypto interface and do not use a static key or early data, // we pass in undefined for these parameters. @@ -269,7 +271,7 @@ export class WebRTCDirectTransport implements Transport { * Generate a noise prologue from the peer connection's certificate. * noise prologue = bytes('libp2p-webrtc-noise:') + noise-responder fingerprint + noise-initiator fingerprint */ - private generateNoisePrologue (pc: RTCPeerConnection, ma: Multiaddr): Uint8Array { + private async generateNoisePrologue (pc: RTCPeerConnection, hasCode: number, ma: Multiaddr): Promise { if (pc.getConfiguration().certificates?.length === 0) { throw invalidArgument('no local certificate') } @@ -283,9 +285,23 @@ export class WebRTCDirectTransport implements Transport { const localFpString = localFingerprint.trim().toLowerCase().replaceAll(':', '') const localFpArray = uint8arrayFromString(localFpString, 'hex') + const local = await encode(hasCode, localFpArray) const remote: Uint8Array = sdp.mbdecoder.decode(sdp.certhash(ma)) const prefix = uint8arrayFromString('libp2p-webrtc-noise:') - return concat([prefix, localFpArray, remote]) + return concat([prefix, local, remote]) + } +} + +async function encode (hasher: number, localFpArray: Uint8Array): Promise { + switch (hasher) { + case 0x11: + return (await sha1.digest(localFpArray)).bytes + case 0x12: + return (await sha256.digest(localFpArray)).bytes + case 0x13: + return (await sha512.digest(localFpArray)).bytes + default: + throw unsupportedHashAlgorithmCode(hasher) } } From 71f4864a1448c1cfd9ac90af527c9d346cb05241 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Sat, 13 Jul 2024 14:20:33 +0200 Subject: [PATCH 5/6] chore: do not double hash --- interop/BrowserDockerfile | 18 +++++++------ interop/Dockerfile | 2 +- .../src/private-to-public/sdp.ts | 11 ++++---- .../src/private-to-public/transport.ts | 26 +++++-------------- 4 files changed, 23 insertions(+), 34 deletions(-) diff --git a/interop/BrowserDockerfile b/interop/BrowserDockerfile index 69aa3b173f..3d379a94b9 100644 --- a/interop/BrowserDockerfile +++ b/interop/BrowserDockerfile @@ -1,14 +1,16 @@ -# syntax=docker/dockerfile:1 +FROM mcr.microsoft.com/playwright -# Copied since we won't have the repo to use if expanding from cache. +WORKDIR /app -# Workaround: https://github.com/docker/cli/issues/996 -ARG BASE_IMAGE=node-js-libp2p-head -FROM ${BASE_IMAGE} as js-libp2p-base +COPY package.json ./ +COPY ./packages ./packages +COPY ./interop ./interop -FROM mcr.microsoft.com/playwright +# disable colored output and CLI animation from test runners +ENV CI=true -COPY --from=js-libp2p-base /app/ /app/ +RUN npm i +RUN npm run build # We install browsers here instead of the cached version so that we use the latest browsers at run time. # Ideally this would also be pinned, but playwright controls this, so there isn't much we can do about it. @@ -17,7 +19,7 @@ RUN npx playwright install-deps RUN npx playwright install # Options: chromium, firefox, webkit -ARG BROWSER=chromium +ARG BROWSER=firefox ENV BROWSER=${BROWSER} WORKDIR /app/interop diff --git a/interop/Dockerfile b/interop/Dockerfile index 84f8e4562b..de4e131fcc 100644 --- a/interop/Dockerfile +++ b/interop/Dockerfile @@ -9,7 +9,7 @@ COPY ./packages ./packages COPY ./interop ./interop # disable colored output and CLI animation from test runners -ENV CI true +ENV CI=true RUN npm i RUN npm run build diff --git a/packages/transport-webrtc/src/private-to-public/sdp.ts b/packages/transport-webrtc/src/private-to-public/sdp.ts index f5bc735fce..ee3f489a5c 100644 --- a/packages/transport-webrtc/src/private-to-public/sdp.ts +++ b/packages/transport-webrtc/src/private-to-public/sdp.ts @@ -4,6 +4,7 @@ import { inappropriateMultiaddr, invalidArgument, invalidFingerprint, unsupporte import { CERTHASH_CODE } from './transport.js' import type { LoggerOptions } from '@libp2p/interface' import type { MultihashDigest } from 'multiformats/hashes/interface' + /** * Get base2 | identity decoders */ @@ -86,20 +87,20 @@ export function ma2Fingerprint (ma: Multiaddr): string[] { throw invalidFingerprint(fingerprint, ma.toString()) } - return [`${prefix.toUpperCase()} ${sdp.join(':').toUpperCase()}`, fingerprint] + return [`${prefix} ${sdp.join(':').toUpperCase()}`, fingerprint] } /** * Normalize the hash name from a given multihash has name */ -export function toSupportedHashFunction (code: number): string { +export function toSupportedHashFunction (code: number): 'SHA-1' | 'SHA-256' | 'SHA-512' { switch (code) { case 0x11: - return 'sha1' + return 'SHA-1' case 0x12: - return 'sha-256' + return 'SHA-256' case 0x13: - return 'sha-512' + return 'SHA-512' default: throw unsupportedHashAlgorithmCode(code) } diff --git a/packages/transport-webrtc/src/private-to-public/transport.ts b/packages/transport-webrtc/src/private-to-public/transport.ts index 6193242f9b..0a9626696f 100644 --- a/packages/transport-webrtc/src/private-to-public/transport.ts +++ b/packages/transport-webrtc/src/private-to-public/transport.ts @@ -3,11 +3,10 @@ import { transportSymbol, serviceCapabilities } from '@libp2p/interface' import * as p from '@libp2p/peer-id' import { protocols } from '@multiformats/multiaddr' import { WebRTCDirect } from '@multiformats/multiaddr-matcher' -import { sha1 } from 'multiformats/hashes/sha1' -import { sha256, sha512 } from 'multiformats/hashes/sha2' +import * as Digest from 'multiformats/hashes/digest' import { concat } from 'uint8arrays/concat' import { fromString as uint8arrayFromString } from 'uint8arrays/from-string' -import { dataChannelError, inappropriateMultiaddr, unimplemented, invalidArgument, unsupportedHashAlgorithmCode } from '../error.js' +import { dataChannelError, inappropriateMultiaddr, unimplemented, invalidArgument } from '../error.js' import { WebRTCMultiaddrConnection } from '../maconn.js' import { DataChannelMuxerFactory } from '../muxer.js' import { createStream } from '../stream.js' @@ -196,7 +195,7 @@ export class WebRTCDirectTransport implements Transport { // Do noise handshake. // Set the Noise Prologue to libp2p-webrtc-noise: before starting the actual Noise handshake. // is the concatenation of the of the two TLS fingerprints of A and B in their multihash byte representation, sorted in ascending order. - const fingerprintsPrologue = await this.generateNoisePrologue(peerConnection, remoteCerthash.code, ma) + const fingerprintsPrologue = this.generateNoisePrologue(peerConnection, remoteCerthash.code, ma) // Since we use the default crypto interface and do not use a static key or early data, // we pass in undefined for these parameters. @@ -274,7 +273,7 @@ export class WebRTCDirectTransport implements Transport { * Generate a noise prologue from the peer connection's certificate. * noise prologue = bytes('libp2p-webrtc-noise:') + noise-responder fingerprint + noise-initiator fingerprint */ - private async generateNoisePrologue (pc: RTCPeerConnection, hasCode: number, ma: Multiaddr): Promise { + private generateNoisePrologue (pc: RTCPeerConnection, hashCode: number, ma: Multiaddr): Uint8Array { if (pc.getConfiguration().certificates?.length === 0) { throw invalidArgument('no local certificate') } @@ -288,23 +287,10 @@ export class WebRTCDirectTransport implements Transport { const localFpString = localFingerprint.trim().toLowerCase().replaceAll(':', '') const localFpArray = uint8arrayFromString(localFpString, 'hex') - const local = await encode(hasCode, localFpArray) + const local = Digest.create(hashCode, localFpArray) const remote: Uint8Array = sdp.mbdecoder.decode(sdp.certhash(ma)) const prefix = uint8arrayFromString('libp2p-webrtc-noise:') - return concat([prefix, local, remote]) - } -} - -async function encode (hasher: number, localFpArray: Uint8Array): Promise { - switch (hasher) { - case 0x11: - return (await sha1.digest(localFpArray)).bytes - case 0x12: - return (await sha256.digest(localFpArray)).bytes - case 0x13: - return (await sha512.digest(localFpArray)).bytes - default: - throw unsupportedHashAlgorithmCode(hasher) + return concat([prefix, local.bytes, remote]) } } From 3b8e306214dc72e7b86b9a47084ab518e80dd168 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Sat, 13 Jul 2024 14:21:12 +0200 Subject: [PATCH 6/6] chore: revert default browser --- interop/BrowserDockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interop/BrowserDockerfile b/interop/BrowserDockerfile index 3d379a94b9..b4a768df14 100644 --- a/interop/BrowserDockerfile +++ b/interop/BrowserDockerfile @@ -19,7 +19,7 @@ RUN npx playwright install-deps RUN npx playwright install # Options: chromium, firefox, webkit -ARG BROWSER=firefox +ARG BROWSER=chromium ENV BROWSER=${BROWSER} WORKDIR /app/interop