Skip to content

Commit 2567a45

Browse files
authored
fix: allow key field to be unset (#118)
Allows field key to not be set in case key can be extracted from the sender's peer ID. (as per https://github.com/libp2p/specs/tree/master/pubsub#message-signing)
1 parent e121e4b commit 2567a45

File tree

2 files changed

+59
-5
lines changed

2 files changed

+59
-5
lines changed

src/utils.ts

+24-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
33
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
44
import { sha256 } from 'multiformats/hashes/sha2'
55
import type { Message, PubSubRPCMessage } from '@libp2p/interface-pubsub'
6-
import { peerIdFromBytes } from '@libp2p/peer-id'
6+
import { peerIdFromBytes, peerIdFromKeys } from '@libp2p/peer-id'
77
import { codes } from './errors.js'
88
import { CodeError } from '@libp2p/interfaces/errors'
99

@@ -66,12 +66,30 @@ export const ensureArray = function <T> (maybeArray: T | T[]) {
6666
return maybeArray
6767
}
6868

69-
export const toMessage = (message: PubSubRPCMessage): Message => {
69+
const isSigned = async (message: PubSubRPCMessage): Promise<boolean> => {
70+
if ((message.sequenceNumber == null) || (message.from == null) || (message.signature == null)) {
71+
return false
72+
}
73+
// if a public key is present in the `from` field, the message should be signed
74+
const fromID = peerIdFromBytes(message.from)
75+
if (fromID.publicKey != null) {
76+
return true
77+
}
78+
79+
if (message.key != null) {
80+
const signingID = await peerIdFromKeys(message.key)
81+
return signingID.equals(fromID)
82+
}
83+
84+
return false
85+
}
86+
87+
export const toMessage = async (message: PubSubRPCMessage): Promise<Message> => {
7088
if (message.from == null) {
7189
throw new CodeError('RPC message was missing from', codes.ERR_MISSING_FROM)
7290
}
7391

74-
if (message.sequenceNumber == null || message.from == null || message.signature == null || message.key == null) {
92+
if (!await isSigned(message)) {
7593
return {
7694
type: 'unsigned',
7795
topic: message.topic ?? '',
@@ -83,9 +101,10 @@ export const toMessage = (message: PubSubRPCMessage): Message => {
83101
type: 'signed',
84102
from: peerIdFromBytes(message.from),
85103
topic: message.topic ?? '',
86-
sequenceNumber: bigIntFromBytes(message.sequenceNumber),
104+
sequenceNumber: bigIntFromBytes(message.sequenceNumber ?? new Uint8Array(0)),
87105
data: message.data ?? new Uint8Array(0),
88-
signature: message.signature,
106+
signature: message.signature ?? new Uint8Array(0),
107+
// @ts-expect-error key need not be defined
89108
key: message.key
90109
}
91110
}

test/utils.spec.ts

+35
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as utils from '../src/utils.js'
33
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
44
import type { Message, PubSubRPCMessage } from '@libp2p/interface-pubsub'
55
import { peerIdFromBytes, peerIdFromString } from '@libp2p/peer-id'
6+
import * as PeerIdFactory from '@libp2p/peer-id-factory'
67

78
describe('utils', () => {
89
it('randomSeqno', () => {
@@ -94,4 +95,38 @@ describe('utils', () => {
9495
expect(utils.bigIntFromBytes(utils.bigIntToBytes(val))).to.equal(val)
9596
})
9697
})
98+
99+
it('ensures message is signed if public key is extractable', async () => {
100+
const dummyPeerID = await PeerIdFactory.createRSAPeerId()
101+
102+
const cases: PubSubRPCMessage[] = [
103+
{
104+
from: (await PeerIdFactory.createSecp256k1PeerId()).toBytes(),
105+
topic: 'test',
106+
data: new Uint8Array(0),
107+
sequenceNumber: utils.bigIntToBytes(1n),
108+
signature: new Uint8Array(0)
109+
},
110+
{
111+
from: peerIdFromString('QmPNdSYk5Rfpo5euNqwtyizzmKXMNHdXeLjTQhcN4yfX22').toBytes(),
112+
topic: 'test',
113+
data: new Uint8Array(0),
114+
sequenceNumber: utils.bigIntToBytes(1n),
115+
signature: new Uint8Array(0)
116+
},
117+
{
118+
from: dummyPeerID.toBytes(),
119+
topic: 'test',
120+
data: new Uint8Array(0),
121+
sequenceNumber: utils.bigIntToBytes(1n),
122+
signature: new Uint8Array(0),
123+
key: dummyPeerID.publicKey
124+
}
125+
]
126+
const expected = ['signed', 'unsigned', 'signed']
127+
128+
const actual = (await Promise.all(cases.map(utils.toMessage))).map(m => m.type)
129+
130+
expect(actual).to.deep.equal(expected)
131+
})
97132
})

0 commit comments

Comments
 (0)