-
Notifications
You must be signed in to change notification settings - Fork 443
/
utils.ts
155 lines (129 loc) · 4.22 KB
/
utils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import { randomBytes } from '@libp2p/crypto'
import { publicKeyFromProtobuf, publicKeyToProtobuf } from '@libp2p/crypto/keys'
import { InvalidMessageError } from '@libp2p/interface'
import { peerIdFromMultihash, peerIdFromPublicKey } from '@libp2p/peer-id'
import * as Digest from 'multiformats/hashes/digest'
import { sha256 } from 'multiformats/hashes/sha2'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import type { Message, PubSubRPCMessage, PublicKey } from '@libp2p/interface'
/**
* Generate a random sequence number
*/
export function randomSeqno (): bigint {
return BigInt(`0x${uint8ArrayToString(randomBytes(8), 'base16')}`)
}
/**
* Generate a message id, based on the `key` and `seqno`
*/
export const msgId = (key: PublicKey, seqno: bigint): Uint8Array => {
const seqnoBytes = uint8ArrayFromString(seqno.toString(16).padStart(16, '0'), 'base16')
const keyBytes = publicKeyToProtobuf(key)
const msgId = new Uint8Array(keyBytes.byteLength + seqnoBytes.length)
msgId.set(keyBytes, 0)
msgId.set(seqnoBytes, keyBytes.byteLength)
return msgId
}
/**
* Generate a message id, based on message `data`
*/
export const noSignMsgId = (data: Uint8Array): Uint8Array | Promise<Uint8Array> => {
return sha256.encode(data)
}
/**
* Check if any member of the first set is also a member
* of the second set
*/
export const anyMatch = (a: Set<number> | number[], b: Set<number> | number[]): boolean => {
let bHas
if (Array.isArray(b)) {
bHas = (val: number) => b.includes(val)
} else {
bHas = (val: number) => b.has(val)
}
for (const val of a) {
if (bHas(val)) {
return true
}
}
return false
}
/**
* Make everything an array
*/
export const ensureArray = function <T> (maybeArray: T | T[]): T[] {
if (!Array.isArray(maybeArray)) {
return [maybeArray]
}
return maybeArray
}
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 = peerIdFromMultihash(Digest.decode(message.from))
if (fromID.publicKey != null) {
return true
}
if (message.key != null) {
const signingKey = message.key
const signingID = peerIdFromPublicKey(publicKeyFromProtobuf(signingKey))
return signingID.equals(fromID)
}
return false
}
export const toMessage = async (message: PubSubRPCMessage): Promise<Message> => {
if (message.from == null) {
throw new InvalidMessageError('RPC message was missing from')
}
if (!await isSigned(message)) {
return {
type: 'unsigned',
topic: message.topic ?? '',
data: message.data ?? new Uint8Array(0)
}
}
const from = peerIdFromMultihash(Digest.decode(message.from))
const key = message.key ?? from.publicKey
if (key == null) {
throw new InvalidMessageError('RPC message was missing public key')
}
const msg: Message = {
type: 'signed',
from,
topic: message.topic ?? '',
sequenceNumber: bigIntFromBytes(message.sequenceNumber ?? new Uint8Array(0)),
data: message.data ?? new Uint8Array(0),
signature: message.signature ?? new Uint8Array(0),
key: key instanceof Uint8Array ? publicKeyFromProtobuf(key) : key
}
return msg
}
export const toRpcMessage = (message: Message): PubSubRPCMessage => {
if (message.type === 'signed') {
return {
from: message.from.toMultihash().bytes,
data: message.data,
sequenceNumber: bigIntToBytes(message.sequenceNumber),
topic: message.topic,
signature: message.signature,
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
key: message.key ? publicKeyToProtobuf(message.key) : undefined
}
}
return {
data: message.data,
topic: message.topic
}
}
export const bigIntToBytes = (num: bigint): Uint8Array => {
let str = num.toString(16)
if (str.length % 2 !== 0) {
str = `0${str}`
}
return uint8ArrayFromString(str, 'base16')
}
export const bigIntFromBytes = (num: Uint8Array): bigint => {
return BigInt(`0x${uint8ArrayToString(num, 'base16')}`)
}