Skip to content

Commit

Permalink
fix: allow key field to be unset (#118)
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
ckousik authored Jan 31, 2023
1 parent e121e4b commit 2567a45
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 5 deletions.
29 changes: 24 additions & 5 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { sha256 } from 'multiformats/hashes/sha2'
import type { Message, PubSubRPCMessage } from '@libp2p/interface-pubsub'
import { peerIdFromBytes } from '@libp2p/peer-id'
import { peerIdFromBytes, peerIdFromKeys } from '@libp2p/peer-id'
import { codes } from './errors.js'
import { CodeError } from '@libp2p/interfaces/errors'

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

export const toMessage = (message: PubSubRPCMessage): Message => {
const isSigned = async (message: PubSubRPCMessage): Promise<boolean> => {
if ((message.sequenceNumber == null) || (message.from == null) || (message.signature == null)) {
return false
}
// if a public key is present in the `from` field, the message should be signed
const fromID = peerIdFromBytes(message.from)
if (fromID.publicKey != null) {
return true
}

if (message.key != null) {
const signingID = await peerIdFromKeys(message.key)
return signingID.equals(fromID)
}

return false
}

export const toMessage = async (message: PubSubRPCMessage): Promise<Message> => {
if (message.from == null) {
throw new CodeError('RPC message was missing from', codes.ERR_MISSING_FROM)
}

if (message.sequenceNumber == null || message.from == null || message.signature == null || message.key == null) {
if (!await isSigned(message)) {
return {
type: 'unsigned',
topic: message.topic ?? '',
Expand All @@ -83,9 +101,10 @@ export const toMessage = (message: PubSubRPCMessage): Message => {
type: 'signed',
from: peerIdFromBytes(message.from),
topic: message.topic ?? '',
sequenceNumber: bigIntFromBytes(message.sequenceNumber),
sequenceNumber: bigIntFromBytes(message.sequenceNumber ?? new Uint8Array(0)),
data: message.data ?? new Uint8Array(0),
signature: message.signature,
signature: message.signature ?? new Uint8Array(0),
// @ts-expect-error key need not be defined
key: message.key
}
}
Expand Down
35 changes: 35 additions & 0 deletions test/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as utils from '../src/utils.js'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import type { Message, PubSubRPCMessage } from '@libp2p/interface-pubsub'
import { peerIdFromBytes, peerIdFromString } from '@libp2p/peer-id'
import * as PeerIdFactory from '@libp2p/peer-id-factory'

describe('utils', () => {
it('randomSeqno', () => {
Expand Down Expand Up @@ -94,4 +95,38 @@ describe('utils', () => {
expect(utils.bigIntFromBytes(utils.bigIntToBytes(val))).to.equal(val)
})
})

it('ensures message is signed if public key is extractable', async () => {
const dummyPeerID = await PeerIdFactory.createRSAPeerId()

const cases: PubSubRPCMessage[] = [
{
from: (await PeerIdFactory.createSecp256k1PeerId()).toBytes(),
topic: 'test',
data: new Uint8Array(0),
sequenceNumber: utils.bigIntToBytes(1n),
signature: new Uint8Array(0)
},
{
from: peerIdFromString('QmPNdSYk5Rfpo5euNqwtyizzmKXMNHdXeLjTQhcN4yfX22').toBytes(),
topic: 'test',
data: new Uint8Array(0),
sequenceNumber: utils.bigIntToBytes(1n),
signature: new Uint8Array(0)
},
{
from: dummyPeerID.toBytes(),
topic: 'test',
data: new Uint8Array(0),
sequenceNumber: utils.bigIntToBytes(1n),
signature: new Uint8Array(0),
key: dummyPeerID.publicKey
}
]
const expected = ['signed', 'unsigned', 'signed']

const actual = (await Promise.all(cases.map(utils.toMessage))).map(m => m.type)

expect(actual).to.deep.equal(expected)
})
})

0 comments on commit 2567a45

Please sign in to comment.