Skip to content

Commit f295fce

Browse files
authored
deps: update it-length-prefixed and uint8arraylists deps (#91)
Update deps to support no-copy operations
1 parent 3251829 commit f295fce

13 files changed

+188
-102
lines changed

package.json

+6-5
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@
180180
"@libp2p/crypto": "^1.0.0",
181181
"@libp2p/interface-connection": "^2.0.0",
182182
"@libp2p/interface-peer-id": "^1.0.2",
183-
"@libp2p/interface-pubsub": "^1.0.3",
183+
"@libp2p/interface-pubsub": "^2.0.0",
184184
"@libp2p/interface-registrar": "^2.0.0",
185185
"@libp2p/interfaces": "^3.0.2",
186186
"@libp2p/logger": "^2.0.0",
@@ -191,11 +191,12 @@
191191
"abortable-iterator": "^4.0.2",
192192
"err-code": "^3.0.1",
193193
"iso-random-stream": "^2.0.0",
194-
"it-length-prefixed": "^7.0.1",
194+
"it-length-prefixed": "^8.0.2",
195195
"it-pipe": "^2.0.3",
196196
"it-pushable": "^3.0.0",
197197
"multiformats": "^9.6.3",
198198
"p-queue": "^7.2.0",
199+
"uint8arraylist": "^2.0.0",
199200
"uint8arrays": "^3.0.0"
200201
},
201202
"devDependencies": {
@@ -204,9 +205,9 @@
204205
"delay": "^5.0.0",
205206
"it-pair": "^2.0.2",
206207
"p-defer": "^4.0.0",
207-
"p-wait-for": "^4.1.0",
208-
"protons": "^3.0.4",
209-
"protons-runtime": "^1.0.4",
208+
"p-wait-for": "^5.0.0",
209+
"protons": "^4.0.1",
210+
"protons-runtime": "^2.0.2",
210211
"sinon": "^14.0.0",
211212
"util": "^0.12.4"
212213
}

src/index.ts

+35-11
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Queue from 'p-queue'
66
import { createTopology } from '@libp2p/topology'
77
import { codes } from './errors.js'
88
import { PeerStreams as PeerStreamsImpl } from './peer-streams.js'
9-
import { toMessage, ensureArray, randomSeqno, noSignMsgId, msgId, toRpcMessage } from './utils.js'
9+
import { toMessage, ensureArray, noSignMsgId, msgId, toRpcMessage, randomSeqno } from './utils.js'
1010
import {
1111
signMessage,
1212
verifySignature
@@ -17,6 +17,7 @@ import type { Connection } from '@libp2p/interface-connection'
1717
import type { PubSub, Message, StrictNoSign, StrictSign, PubSubInit, PubSubEvents, PeerStreams, PubSubRPCMessage, PubSubRPC, PubSubRPCSubscription, SubscriptionChangeData, PublishResult } from '@libp2p/interface-pubsub'
1818
import { PeerMap, PeerSet } from '@libp2p/peer-collections'
1919
import { Components, Initializable } from '@libp2p/components'
20+
import type { Uint8ArrayList } from 'uint8arraylist'
2021

2122
const log = logger('libp2p:pubsub')
2223

@@ -284,7 +285,7 @@ export abstract class PubSubBaseProtocol<Events = PubSubEvents> extends EventEmi
284285
/**
285286
* Responsible for processing each RPC message received by other peers.
286287
*/
287-
async processMessages (peerId: PeerId, stream: AsyncIterable<Uint8Array>, peerStreams: PeerStreams) {
288+
async processMessages (peerId: PeerId, stream: AsyncIterable<Uint8ArrayList>, peerStreams: PeerStreams) {
288289
try {
289290
await pipe(
290291
stream,
@@ -446,6 +447,10 @@ export abstract class PubSubBaseProtocol<Events = PubSubEvents> extends EventEmi
446447
const signaturePolicy = this.globalSignaturePolicy
447448
switch (signaturePolicy) {
448449
case 'StrictSign':
450+
if (msg.type !== 'signed') {
451+
throw errcode(new Error('Message type should be "signed" when signature policy is StrictSign but it was not'), codes.ERR_MISSING_SIGNATURE)
452+
}
453+
449454
if (msg.sequenceNumber == null) {
450455
throw errcode(new Error('Need seqno when signature policy is StrictSign but it was missing'), codes.ERR_MISSING_SEQNO)
451456
}
@@ -474,19 +479,19 @@ export abstract class PubSubBaseProtocol<Events = PubSubEvents> extends EventEmi
474479
* Decode Uint8Array into an RPC object.
475480
* This can be override to use a custom router protobuf.
476481
*/
477-
abstract decodeRpc (bytes: Uint8Array): PubSubRPC
482+
abstract decodeRpc (bytes: Uint8Array | Uint8ArrayList): PubSubRPC
478483

479484
/**
480485
* Encode RPC object into a Uint8Array.
481486
* This can be override to use a custom router protobuf.
482487
*/
483-
abstract encodeRpc (rpc: PubSubRPC): Uint8Array
488+
abstract encodeRpc (rpc: PubSubRPC): Uint8ArrayList
484489

485490
/**
486491
* Encode RPC object into a Uint8Array.
487492
* This can be override to use a custom router protobuf.
488493
*/
489-
abstract encodeMessage (rpc: PubSubRPCMessage): Uint8Array
494+
abstract encodeMessage (rpc: PubSubRPCMessage): Uint8ArrayList
490495

491496
/**
492497
* Send an rpc object to a peer
@@ -523,26 +528,42 @@ export abstract class PubSubBaseProtocol<Events = PubSubEvents> extends EventEmi
523528
const signaturePolicy = this.globalSignaturePolicy
524529
switch (signaturePolicy) {
525530
case 'StrictNoSign':
531+
if (message.type !== 'unsigned') {
532+
throw errcode(new Error('Message type should be "unsigned" when signature policy is StrictNoSign but it was not'), codes.ERR_MISSING_SIGNATURE)
533+
}
534+
535+
// @ts-expect-error should not be present
526536
if (message.signature != null) {
527537
throw errcode(new Error('StrictNoSigning: signature should not be present'), codes.ERR_UNEXPECTED_SIGNATURE)
528538
}
539+
540+
// @ts-expect-error should not be present
529541
if (message.key != null) {
530542
throw errcode(new Error('StrictNoSigning: key should not be present'), codes.ERR_UNEXPECTED_KEY)
531543
}
544+
545+
// @ts-expect-error should not be present
532546
if (message.sequenceNumber != null) {
533547
throw errcode(new Error('StrictNoSigning: seqno should not be present'), codes.ERR_UNEXPECTED_SEQNO)
534548
}
535549
break
536550
case 'StrictSign':
551+
if (message.type !== 'signed') {
552+
throw errcode(new Error('Message type should be "signed" when signature policy is StrictSign but it was not'), codes.ERR_MISSING_SIGNATURE)
553+
}
554+
537555
if (message.signature == null) {
538556
throw errcode(new Error('StrictSigning: Signing required and no signature was present'), codes.ERR_MISSING_SIGNATURE)
539557
}
558+
540559
if (message.sequenceNumber == null) {
541-
throw errcode(new Error('StrictSigning: Signing required and no seqno was present'), codes.ERR_MISSING_SEQNO)
560+
throw errcode(new Error('StrictSigning: Signing required and no sequenceNumber was present'), codes.ERR_MISSING_SEQNO)
542561
}
562+
543563
if (!(await verifySignature(message, this.encodeMessage.bind(this)))) {
544564
throw errcode(new Error('StrictSigning: Invalid message signature'), codes.ERR_INVALID_SIGNATURE)
545565
}
566+
546567
break
547568
default:
548569
throw errcode(new Error('Cannot validate message: unhandled signature policy'), codes.ERR_UNHANDLED_SIGNATURE_POLICY)
@@ -559,14 +580,16 @@ export abstract class PubSubBaseProtocol<Events = PubSubEvents> extends EventEmi
559580
* Normalizes the message and signs it, if signing is enabled.
560581
* Should be used by the routers to create the message to send.
561582
*/
562-
async buildMessage (message: Message) {
583+
async buildMessage (message: { from: PeerId, topic: string, data: Uint8Array, sequenceNumber: bigint }): Promise<Message> {
563584
const signaturePolicy = this.globalSignaturePolicy
564585
switch (signaturePolicy) {
565586
case 'StrictSign':
566-
message.sequenceNumber = randomSeqno()
567587
return await signMessage(this.components.getPeerId(), message, this.encodeMessage.bind(this))
568588
case 'StrictNoSign':
569-
return await Promise.resolve(message)
589+
return await Promise.resolve({
590+
type: 'unsigned',
591+
...message
592+
})
570593
default:
571594
throw errcode(new Error('Cannot build message: unhandled signature policy'), codes.ERR_UNHANDLED_SIGNATURE_POLICY)
572595
}
@@ -603,10 +626,11 @@ export abstract class PubSubBaseProtocol<Events = PubSubEvents> extends EventEmi
603626
throw new Error('Pubsub has not started')
604627
}
605628

606-
const message: Message = {
629+
const message = {
607630
from: this.components.getPeerId(),
608631
topic,
609-
data: data ?? new Uint8Array(0)
632+
data: data ?? new Uint8Array(0),
633+
sequenceNumber: randomSeqno()
610634
}
611635

612636
log('publish topic: %s from: %p data: %m', topic, message.from, message.data)

src/peer-streams.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { PeerId } from '@libp2p/interface-peer-id'
88
import type { Stream } from '@libp2p/interface-connection'
99
import type { Pushable } from 'it-pushable'
1010
import type { PeerStreamEvents } from '@libp2p/interface-pubsub'
11+
import { Uint8ArrayList } from 'uint8arraylist'
1112

1213
const log = logger('libp2p-pubsub:peer-streams')
1314

@@ -25,11 +26,11 @@ export class PeerStreams extends EventEmitter<PeerStreamEvents> {
2526
/**
2627
* Write stream - it's preferable to use the write method
2728
*/
28-
public outboundStream?: Pushable<Uint8Array>
29+
public outboundStream?: Pushable<Uint8ArrayList>
2930
/**
3031
* Read stream
3132
*/
32-
public inboundStream?: AsyncIterable<Uint8Array>
33+
public inboundStream?: AsyncIterable<Uint8ArrayList>
3334
/**
3435
* The raw outbound stream, as retrieved from conn.newStream
3536
*/
@@ -72,13 +73,13 @@ export class PeerStreams extends EventEmitter<PeerStreamEvents> {
7273
* Send a message to this peer.
7374
* Throws if there is no `stream` to write to available.
7475
*/
75-
write (data: Uint8Array) {
76+
write (data: Uint8Array | Uint8ArrayList) {
7677
if (this.outboundStream == null) {
7778
const id = this.id.toString()
7879
throw new Error('No writable connection to ' + id)
7980
}
8081

81-
this.outboundStream.push(data)
82+
this.outboundStream.push(data instanceof Uint8Array ? new Uint8ArrayList(data) : data)
8283
}
8384

8485
/**
@@ -115,7 +116,8 @@ export class PeerStreams extends EventEmitter<PeerStreamEvents> {
115116
}
116117

117118
this._rawOutboundStream = stream
118-
this.outboundStream = pushable({
119+
this.outboundStream = pushable<Uint8ArrayList>({
120+
objectMode: true,
119121
onEnd: (shouldEmit) => {
120122
// close writable side of the stream
121123
if (this._rawOutboundStream != null && this._rawOutboundStream.reset != null) { // eslint-disable-line @typescript-eslint/prefer-optional-chain

src/sign.ts

+31-18
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,16 @@ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
33
import { toRpcMessage } from './utils.js'
44
import type { PeerId } from '@libp2p/interface-peer-id'
55
import { keys } from '@libp2p/crypto'
6-
import type { Message, PubSubRPCMessage } from '@libp2p/interface-pubsub'
6+
import type { PubSubRPCMessage, SignedMessage } from '@libp2p/interface-pubsub'
77
import { peerIdFromKeys } from '@libp2p/peer-id'
8+
import type { Uint8ArrayList } from 'uint8arraylist'
89

910
export const SignPrefix = uint8ArrayFromString('libp2p-pubsub:')
1011

1112
/**
1213
* Signs the provided message with the given `peerId`
1314
*/
14-
export async function signMessage (peerId: PeerId, message: Message, encode: (rpc: PubSubRPCMessage) => Uint8Array) {
15-
// Get the message in bytes, and prepend with the pubsub prefix
16-
const bytes = uint8ArrayConcat([
17-
SignPrefix,
18-
encode(toRpcMessage(message))
19-
])
20-
15+
export async function signMessage (peerId: PeerId, message: { from: PeerId, topic: string, data: Uint8Array, sequenceNumber: bigint }, encode: (rpc: PubSubRPCMessage) => Uint8ArrayList): Promise<SignedMessage> {
2116
if (peerId.privateKey == null) {
2217
throw new Error('Cannot sign message, no private key present')
2318
}
@@ -26,22 +21,36 @@ export async function signMessage (peerId: PeerId, message: Message, encode: (rp
2621
throw new Error('Cannot sign message, no public key present')
2722
}
2823

29-
const privateKey = await keys.unmarshalPrivateKey(peerId.privateKey)
30-
const signature = await privateKey.sign(bytes)
31-
32-
const outputMessage: Message = {
33-
...message,
34-
signature: signature,
35-
key: peerId.publicKey
24+
// @ts-expect-error signature field is missing, added below
25+
const outputMessage: SignedMessage = {
26+
type: 'signed',
27+
topic: message.topic,
28+
data: message.data,
29+
sequenceNumber: message.sequenceNumber,
30+
from: peerId
3631
}
3732

33+
// Get the message in bytes, and prepend with the pubsub prefix
34+
const bytes = uint8ArrayConcat([
35+
SignPrefix,
36+
encode(toRpcMessage(outputMessage)).subarray()
37+
])
38+
39+
const privateKey = await keys.unmarshalPrivateKey(peerId.privateKey)
40+
outputMessage.signature = await privateKey.sign(bytes)
41+
outputMessage.key = peerId.publicKey
42+
3843
return outputMessage
3944
}
4045

4146
/**
4247
* Verifies the signature of the given message
4348
*/
44-
export async function verifySignature (message: Message, encode: (rpc: PubSubRPCMessage) => Uint8Array) {
49+
export async function verifySignature (message: SignedMessage, encode: (rpc: PubSubRPCMessage) => Uint8ArrayList) {
50+
if (message.type !== 'signed') {
51+
throw new Error('Message type must be "signed" to be verified')
52+
}
53+
4554
if (message.signature == null) {
4655
throw new Error('Message must contain a signature to be verified')
4756
}
@@ -57,7 +66,7 @@ export async function verifySignature (message: Message, encode: (rpc: PubSubRPC
5766
...toRpcMessage(message),
5867
signature: undefined,
5968
key: undefined
60-
})
69+
}).subarray()
6170
])
6271

6372
// Get the public key
@@ -72,7 +81,11 @@ export async function verifySignature (message: Message, encode: (rpc: PubSubRPC
7281
* Returns the PublicKey associated with the given message.
7382
* If no valid PublicKey can be retrieved an error will be returned.
7483
*/
75-
export async function messagePublicKey (message: Message) {
84+
export async function messagePublicKey (message: SignedMessage) {
85+
if (message.type !== 'signed') {
86+
throw new Error('Message type must be "signed" to have a public key')
87+
}
88+
7689
// should be available in the from property of the message (peer id)
7790
if (message.from == null) {
7891
throw new Error('Could not get the public key from the originator id')

src/utils.ts

+24-8
Original file line numberDiff line numberDiff line change
@@ -71,24 +71,40 @@ export const toMessage = (message: PubSubRPCMessage): Message => {
7171
throw errcode(new Error('RPC message was missing from'), codes.ERR_MISSING_FROM)
7272
}
7373

74+
if (message.sequenceNumber == null || message.from == null || message.signature == null || message.key == null) {
75+
return {
76+
type: 'unsigned',
77+
topic: message.topic ?? '',
78+
data: message.data ?? new Uint8Array(0)
79+
}
80+
}
81+
7482
return {
83+
type: 'signed',
7584
from: peerIdFromBytes(message.from),
7685
topic: message.topic ?? '',
77-
sequenceNumber: message.sequenceNumber == null ? undefined : bigIntFromBytes(message.sequenceNumber),
86+
sequenceNumber: bigIntFromBytes(message.sequenceNumber),
7887
data: message.data ?? new Uint8Array(0),
79-
signature: message.signature ?? undefined,
80-
key: message.key ?? undefined
88+
signature: message.signature,
89+
key: message.key
8190
}
8291
}
8392

8493
export const toRpcMessage = (message: Message): PubSubRPCMessage => {
94+
if (message.type === 'signed') {
95+
return {
96+
from: message.from.multihash.bytes,
97+
data: message.data,
98+
sequenceNumber: bigIntToBytes(message.sequenceNumber),
99+
topic: message.topic,
100+
signature: message.signature,
101+
key: message.key
102+
}
103+
}
104+
85105
return {
86-
from: message.from.multihash.bytes,
87106
data: message.data,
88-
sequenceNumber: message.sequenceNumber == null ? undefined : bigIntToBytes(message.sequenceNumber),
89-
topic: message.topic,
90-
signature: message.signature,
91-
key: message.key
107+
topic: message.topic
92108
}
93109
}
94110

test/instance.spec.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
import { expect } from 'aegir/chai'
22
import { PubSubBaseProtocol } from '../src/index.js'
33
import type { PublishResult, PubSubRPC, PubSubRPCMessage } from '@libp2p/interface-pubsub'
4+
import type { Uint8ArrayList } from 'uint8arraylist'
45

56
class PubsubProtocol extends PubSubBaseProtocol {
67
decodeRpc (bytes: Uint8Array): PubSubRPC {
78
throw new Error('Method not implemented.')
89
}
910

10-
encodeRpc (rpc: PubSubRPC): Uint8Array {
11+
encodeRpc (rpc: PubSubRPC): Uint8ArrayList {
1112
throw new Error('Method not implemented.')
1213
}
1314

14-
decodeMessage (bytes: Uint8Array): PubSubRPCMessage {
15+
decodeMessage (bytes: Uint8Array | Uint8ArrayList): PubSubRPCMessage {
1516
throw new Error('Method not implemented.')
1617
}
1718

18-
encodeMessage (rpc: PubSubRPCMessage): Uint8Array {
19+
encodeMessage (rpc: PubSubRPCMessage): Uint8ArrayList {
1920
throw new Error('Method not implemented.')
2021
}
2122

0 commit comments

Comments
 (0)