@waku/core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/CHANGELOG.md +614 -0
  2. package/README.md +56 -0
  3. package/bundle/browser-1e1a2f27.js +722 -0
  4. package/bundle/crypto-8551d579.js +2585 -0
  5. package/bundle/crypto-b00764b7.js +1772 -0
  6. package/bundle/enr-564d4a51.js +20785 -0
  7. package/bundle/enr-9fc5eed8.js +20786 -0
  8. package/bundle/enr-f6e82a53.js +20785 -0
  9. package/bundle/events-158407bb.js +1929 -0
  10. package/bundle/events-fcbda4dc.js +76 -0
  11. package/bundle/index-02d21809.js +20 -0
  12. package/bundle/index-0a4bdddc.js +2976 -0
  13. package/bundle/index-2ae915be.js +1854 -0
  14. package/bundle/index-64ce43f0.js +69 -0
  15. package/bundle/index-691c0be6.js +4059 -0
  16. package/bundle/index-a013a259.js +20 -0
  17. package/bundle/index-ba42b4fc.js +862 -0
  18. package/bundle/index.js +13428 -0
  19. package/bundle/lib/enr.js +8 -0
  20. package/bundle/lib/peer_discovery_dns.js +5018 -0
  21. package/bundle/lib/peer_discovery_static_list.js +75 -0
  22. package/bundle/lib/predefined_bootstrap_nodes.js +59 -0
  23. package/bundle/lib/utils.js +1 -0
  24. package/bundle/lib/wait_for_remote_peer.js +327 -0
  25. package/bundle/lib/waku_message/topic_only_message.js +4 -0
  26. package/bundle/lib/waku_message/version_0.js +4 -0
  27. package/bundle/lib/waku_message/version_1.js +463 -0
  28. package/bundle/message-e2db79d7.js +8393 -0
  29. package/bundle/multiaddr_to_peer_info-c406b1e1.js +19 -0
  30. package/bundle/multiaddr_to_peer_info-fd1de516.js +19 -0
  31. package/bundle/random_subset-75d1c511.js +26 -0
  32. package/bundle/topic_only_message-34f36fa6.js +82 -0
  33. package/bundle/utils-9a3221f2.js +815 -0
  34. package/bundle/version_0-e6fe440c.js +317 -0
  35. package/dist/index.d.ts +16 -0
  36. package/dist/index.js +17 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/lib/constants.d.ts +4 -0
  39. package/dist/lib/constants.js +5 -0
  40. package/dist/lib/constants.js.map +1 -0
  41. package/dist/lib/crypto.d.ts +34 -0
  42. package/dist/lib/crypto.js +79 -0
  43. package/dist/lib/crypto.js.map +1 -0
  44. package/dist/lib/enr/constants.d.ts +4 -0
  45. package/dist/lib/enr/constants.js +8 -0
  46. package/dist/lib/enr/constants.js.map +1 -0
  47. package/dist/lib/enr/enr.d.ts +90 -0
  48. package/dist/lib/enr/enr.js +432 -0
  49. package/dist/lib/enr/enr.js.map +1 -0
  50. package/dist/lib/enr/index.d.ts +5 -0
  51. package/dist/lib/enr/index.js +6 -0
  52. package/dist/lib/enr/index.js.map +1 -0
  53. package/dist/lib/enr/keypair/index.d.ts +8 -0
  54. package/dist/lib/enr/keypair/index.js +53 -0
  55. package/dist/lib/enr/keypair/index.js.map +1 -0
  56. package/dist/lib/enr/keypair/secp256k1.d.ts +13 -0
  57. package/dist/lib/enr/keypair/secp256k1.js +57 -0
  58. package/dist/lib/enr/keypair/secp256k1.js.map +1 -0
  59. package/dist/lib/enr/keypair/types.d.ts +13 -0
  60. package/dist/lib/enr/keypair/types.js +7 -0
  61. package/dist/lib/enr/keypair/types.js.map +1 -0
  62. package/dist/lib/enr/multiaddr_from_fields.d.ts +2 -0
  63. package/dist/lib/enr/multiaddr_from_fields.js +8 -0
  64. package/dist/lib/enr/multiaddr_from_fields.js.map +1 -0
  65. package/dist/lib/enr/multiaddrs_codec.d.ts +3 -0
  66. package/dist/lib/enr/multiaddrs_codec.js +32 -0
  67. package/dist/lib/enr/multiaddrs_codec.js.map +1 -0
  68. package/dist/lib/enr/types.d.ts +8 -0
  69. package/dist/lib/enr/types.js +3 -0
  70. package/dist/lib/enr/types.js.map +1 -0
  71. package/dist/lib/enr/v4.d.ts +3 -0
  72. package/dist/lib/enr/v4.js +14 -0
  73. package/dist/lib/enr/v4.js.map +1 -0
  74. package/dist/lib/enr/waku2_codec.d.ts +8 -0
  75. package/dist/lib/enr/waku2_codec.js +36 -0
  76. package/dist/lib/enr/waku2_codec.js.map +1 -0
  77. package/dist/lib/group_by.d.ts +3 -0
  78. package/dist/lib/group_by.js +13 -0
  79. package/dist/lib/group_by.js.map +1 -0
  80. package/dist/lib/multiaddr_to_peer_info.d.ts +3 -0
  81. package/dist/lib/multiaddr_to_peer_info.js +15 -0
  82. package/dist/lib/multiaddr_to_peer_info.js.map +1 -0
  83. package/dist/lib/peer_discovery_dns/dns.d.ts +48 -0
  84. package/dist/lib/peer_discovery_dns/dns.js +158 -0
  85. package/dist/lib/peer_discovery_dns/dns.js.map +1 -0
  86. package/dist/lib/peer_discovery_dns/dns_over_https.d.ts +32 -0
  87. package/dist/lib/peer_discovery_dns/dns_over_https.js +87 -0
  88. package/dist/lib/peer_discovery_dns/dns_over_https.js.map +1 -0
  89. package/dist/lib/peer_discovery_dns/enrtree.d.ts +33 -0
  90. package/dist/lib/peer_discovery_dns/enrtree.js +76 -0
  91. package/dist/lib/peer_discovery_dns/enrtree.js.map +1 -0
  92. package/dist/lib/peer_discovery_dns/fetch_nodes.d.ts +14 -0
  93. package/dist/lib/peer_discovery_dns/fetch_nodes.js +133 -0
  94. package/dist/lib/peer_discovery_dns/fetch_nodes.js.map +1 -0
  95. package/dist/lib/peer_discovery_dns/index.d.ts +30 -0
  96. package/dist/lib/peer_discovery_dns/index.js +54 -0
  97. package/dist/lib/peer_discovery_dns/index.js.map +1 -0
  98. package/dist/lib/peer_discovery_static_list.d.ts +44 -0
  99. package/dist/lib/peer_discovery_static_list.js +72 -0
  100. package/dist/lib/peer_discovery_static_list.js.map +1 -0
  101. package/dist/lib/predefined_bootstrap_nodes.d.ts +35 -0
  102. package/dist/lib/predefined_bootstrap_nodes.js +56 -0
  103. package/dist/lib/predefined_bootstrap_nodes.js.map +1 -0
  104. package/dist/lib/push_or_init_map.d.ts +1 -0
  105. package/dist/lib/push_or_init_map.js +9 -0
  106. package/dist/lib/push_or_init_map.js.map +1 -0
  107. package/dist/lib/random_subset.d.ts +4 -0
  108. package/dist/lib/random_subset.js +25 -0
  109. package/dist/lib/random_subset.js.map +1 -0
  110. package/dist/lib/select_connection.d.ts +2 -0
  111. package/dist/lib/select_connection.js +19 -0
  112. package/dist/lib/select_connection.js.map +1 -0
  113. package/dist/lib/select_peer.d.ts +15 -0
  114. package/dist/lib/select_peer.js +59 -0
  115. package/dist/lib/select_peer.js.map +1 -0
  116. package/dist/lib/to_proto_message.d.ts +3 -0
  117. package/dist/lib/to_proto_message.js +11 -0
  118. package/dist/lib/to_proto_message.js.map +1 -0
  119. package/dist/lib/utils.d.ts +22 -0
  120. package/dist/lib/utils.js +40 -0
  121. package/dist/lib/utils.js.map +1 -0
  122. package/dist/lib/wait_for_remote_peer.d.ts +22 -0
  123. package/dist/lib/wait_for_remote_peer.js +113 -0
  124. package/dist/lib/wait_for_remote_peer.js.map +1 -0
  125. package/dist/lib/waku.d.ts +61 -0
  126. package/dist/lib/waku.js +174 -0
  127. package/dist/lib/waku.js.map +1 -0
  128. package/dist/lib/waku_filter/filter_rpc.d.ts +25 -0
  129. package/dist/lib/waku_filter/filter_rpc.js +44 -0
  130. package/dist/lib/waku_filter/filter_rpc.js.map +1 -0
  131. package/dist/lib/waku_filter/index.d.ts +50 -0
  132. package/dist/lib/waku_filter/index.js +181 -0
  133. package/dist/lib/waku_filter/index.js.map +1 -0
  134. package/dist/lib/waku_light_push/index.d.ts +38 -0
  135. package/dist/lib/waku_light_push/index.js +83 -0
  136. package/dist/lib/waku_light_push/index.js.map +1 -0
  137. package/dist/lib/waku_light_push/push_rpc.d.ts +11 -0
  138. package/dist/lib/waku_light_push/push_rpc.js +31 -0
  139. package/dist/lib/waku_light_push/push_rpc.js.map +1 -0
  140. package/dist/lib/waku_message/constants.d.ts +12 -0
  141. package/dist/lib/waku_message/constants.js +10 -0
  142. package/dist/lib/waku_message/constants.js.map +1 -0
  143. package/dist/lib/waku_message/ecies.d.ts +17 -0
  144. package/dist/lib/waku_message/ecies.js +126 -0
  145. package/dist/lib/waku_message/ecies.js.map +1 -0
  146. package/dist/lib/waku_message/symmetric.d.ts +3 -0
  147. package/dist/lib/waku_message/symmetric.js +18 -0
  148. package/dist/lib/waku_message/symmetric.js.map +1 -0
  149. package/dist/lib/waku_message/topic_only_message.d.ts +15 -0
  150. package/dist/lib/waku_message/topic_only_message.js +31 -0
  151. package/dist/lib/waku_message/topic_only_message.js.map +1 -0
  152. package/dist/lib/waku_message/version_0.d.ts +26 -0
  153. package/dist/lib/waku_message/version_0.js +96 -0
  154. package/dist/lib/waku_message/version_0.js.map +1 -0
  155. package/dist/lib/waku_message/version_1.d.ts +93 -0
  156. package/dist/lib/waku_message/version_1.js +325 -0
  157. package/dist/lib/waku_message/version_1.js.map +1 -0
  158. package/dist/lib/waku_relay/constants.d.ts +63 -0
  159. package/dist/lib/waku_relay/constants.js +67 -0
  160. package/dist/lib/waku_relay/constants.js.map +1 -0
  161. package/dist/lib/waku_relay/index.d.ts +65 -0
  162. package/dist/lib/waku_relay/index.js +111 -0
  163. package/dist/lib/waku_relay/index.js.map +1 -0
  164. package/dist/lib/waku_store/history_rpc.d.ts +27 -0
  165. package/dist/lib/waku_store/history_rpc.js +71 -0
  166. package/dist/lib/waku_store/history_rpc.js.map +1 -0
  167. package/dist/lib/waku_store/index.d.ts +126 -0
  168. package/dist/lib/waku_store/index.js +218 -0
  169. package/dist/lib/waku_store/index.js.map +1 -0
  170. package/dist/proto/filter.d.ts +65 -0
  171. package/dist/proto/filter.js +425 -0
  172. package/dist/proto/filter.js.map +1 -0
  173. package/dist/proto/light_push.d.ts +57 -0
  174. package/dist/proto/light_push.js +369 -0
  175. package/dist/proto/light_push.js.map +1 -0
  176. package/dist/proto/message.d.ts +29 -0
  177. package/dist/proto/message.js +215 -0
  178. package/dist/proto/message.js.map +1 -0
  179. package/dist/proto/store.d.ts +104 -0
  180. package/dist/proto/store.js +602 -0
  181. package/dist/proto/store.js.map +1 -0
  182. package/dist/proto/topic_only_message.d.ts +10 -0
  183. package/dist/proto/topic_only_message.js +46 -0
  184. package/dist/proto/topic_only_message.js.map +1 -0
  185. package/package.json +292 -0
  186. package/src/index.ts +33 -0
  187. package/src/lib/constants.ts +4 -0
  188. package/src/lib/crypto.ts +100 -0
  189. package/src/lib/enr/constants.ts +10 -0
  190. package/src/lib/enr/enr.ts +516 -0
  191. package/src/lib/enr/index.ts +5 -0
  192. package/src/lib/enr/keypair/index.ts +76 -0
  193. package/src/lib/enr/keypair/secp256k1.ts +69 -0
  194. package/src/lib/enr/keypair/types.ts +14 -0
  195. package/src/lib/enr/multiaddr_from_fields.ts +18 -0
  196. package/src/lib/enr/multiaddrs_codec.ts +50 -0
  197. package/src/lib/enr/types.ts +11 -0
  198. package/src/lib/enr/v4.ts +22 -0
  199. package/src/lib/enr/waku2_codec.ts +39 -0
  200. package/src/lib/group_by.ts +14 -0
  201. package/src/lib/multiaddr_to_peer_info.ts +17 -0
  202. package/src/lib/peer_discovery_dns/dns.ts +223 -0
  203. package/src/lib/peer_discovery_dns/dns_over_https.ts +98 -0
  204. package/src/lib/peer_discovery_dns/enrtree.ts +123 -0
  205. package/src/lib/peer_discovery_dns/fetch_nodes.ts +180 -0
  206. package/src/lib/peer_discovery_dns/index.ts +84 -0
  207. package/src/lib/peer_discovery_static_list.ts +118 -0
  208. package/src/lib/predefined_bootstrap_nodes.ts +72 -0
  209. package/src/lib/push_or_init_map.ts +13 -0
  210. package/src/lib/random_subset.ts +30 -0
  211. package/src/lib/select_connection.ts +24 -0
  212. package/src/lib/select_peer.ts +77 -0
  213. package/src/lib/to_proto_message.ts +15 -0
  214. package/src/lib/utils.ts +50 -0
  215. package/src/lib/wait_for_remote_peer.ts +151 -0
  216. package/src/lib/waku.ts +258 -0
  217. package/src/lib/waku_filter/filter_rpc.ts +57 -0
  218. package/src/lib/waku_filter/index.ts +291 -0
  219. package/src/lib/waku_light_push/index.ts +137 -0
  220. package/src/lib/waku_light_push/push_rpc.ts +39 -0
  221. package/src/lib/waku_message/constants.ts +10 -0
  222. package/src/lib/waku_message/ecies.ts +194 -0
  223. package/src/lib/waku_message/symmetric.ts +33 -0
  224. package/src/lib/waku_message/topic_only_message.ts +40 -0
  225. package/src/lib/waku_message/version_0.ts +121 -0
  226. package/src/lib/waku_message/version_1.ts +457 -0
  227. package/src/lib/waku_relay/constants.ts +77 -0
  228. package/src/lib/waku_relay/index.ts +189 -0
  229. package/src/lib/waku_store/history_rpc.ts +94 -0
  230. package/src/lib/waku_store/index.ts +372 -0
  231. package/src/proto/filter.ts +602 -0
  232. package/src/proto/light_push.ts +526 -0
  233. package/src/proto/message.ts +304 -0
  234. package/src/proto/store.ts +844 -0
  235. package/src/proto/topic_only_message.ts +67 -0
@@ -0,0 +1,291 @@
1
+ import type { Stream } from "@libp2p/interface-connection";
2
+ import type { PeerId } from "@libp2p/interface-peer-id";
3
+ import type { Peer } from "@libp2p/interface-peer-store";
4
+ import type { IncomingStreamData } from "@libp2p/interface-registrar";
5
+ import type {
6
+ Callback,
7
+ Decoder,
8
+ Filter,
9
+ Message,
10
+ ProtocolOptions,
11
+ } from "@waku/interfaces";
12
+ import debug from "debug";
13
+ import all from "it-all";
14
+ import * as lp from "it-length-prefixed";
15
+ import { pipe } from "it-pipe";
16
+ import type { Libp2p } from "libp2p";
17
+
18
+ import { WakuMessage as WakuMessageProto } from "../../proto/message";
19
+ import { DefaultPubSubTopic } from "../constants";
20
+ import { groupByContentTopic } from "../group_by";
21
+ import { selectConnection } from "../select_connection";
22
+ import {
23
+ getPeersForProtocol,
24
+ selectPeerForProtocol,
25
+ selectRandomPeer,
26
+ } from "../select_peer";
27
+ import { toProtoMessage } from "../to_proto_message";
28
+
29
+ import { ContentFilter, FilterRPC } from "./filter_rpc";
30
+ export { ContentFilter };
31
+
32
+ export const FilterCodec = "/vac/waku/filter/2.0.0-beta1";
33
+
34
+ const log = debug("waku:filter");
35
+
36
+ export interface CreateOptions {
37
+ /**
38
+ * The PubSub Topic to use. Defaults to {@link DefaultPubSubTopic}.
39
+ *
40
+ * The usage of the default pubsub topic is recommended.
41
+ * See [Waku v2 Topic Usage Recommendations](https://rfc.vac.dev/spec/23/) for details.
42
+ *
43
+ * @default {@link DefaultPubSubTopic}
44
+ */
45
+ pubSubTopic?: string;
46
+ }
47
+
48
+ export type UnsubscribeFunction = () => Promise<void>;
49
+
50
+ /**
51
+ * Implements client side of the [Waku v2 Filter protocol](https://rfc.vac.dev/spec/12/).
52
+ *
53
+ * Note this currently only works in NodeJS when the Waku node is listening on a port, see:
54
+ * - https://github.com/status-im/go-waku/issues/245
55
+ * - https://github.com/status-im/nwaku/issues/948
56
+ */
57
+ export class WakuFilter implements Filter {
58
+ pubSubTopic: string;
59
+ private subscriptions: Map<string, Callback<any>>;
60
+ private decoders: Map<
61
+ string, // content topic
62
+ Set<Decoder<any>>
63
+ >;
64
+
65
+ constructor(public libp2p: Libp2p, options?: CreateOptions) {
66
+ this.subscriptions = new Map();
67
+ this.decoders = new Map();
68
+ this.pubSubTopic = options?.pubSubTopic ?? DefaultPubSubTopic;
69
+ this.libp2p
70
+ .handle(FilterCodec, this.onRequest.bind(this))
71
+ .catch((e) => log("Failed to register filter protocol", e));
72
+ }
73
+
74
+ /**
75
+ * @param decoders Array of Decoders to use to decode messages, it also specifies the content topics.
76
+ * @param callback A function that will be called on each message returned by the filter.
77
+ * @param opts The FilterSubscriptionOpts used to narrow which messages are returned, and which peer to connect to.
78
+ * @returns Unsubscribe function that can be used to end the subscription.
79
+ */
80
+ async subscribe<T extends Message>(
81
+ decoders: Decoder<T>[],
82
+ callback: Callback<T>,
83
+ opts?: ProtocolOptions
84
+ ): Promise<UnsubscribeFunction> {
85
+ const topic = opts?.pubSubTopic ?? this.pubSubTopic;
86
+
87
+ const groupedDecoders = groupByContentTopic(decoders);
88
+ const contentTopics = Array.from(groupedDecoders.keys());
89
+
90
+ const contentFilters = contentTopics.map((contentTopic) => ({
91
+ contentTopic,
92
+ }));
93
+ const request = FilterRPC.createRequest(
94
+ topic,
95
+ contentFilters,
96
+ undefined,
97
+ true
98
+ );
99
+
100
+ const requestId = request.requestId;
101
+ if (!requestId)
102
+ throw new Error(
103
+ "Internal error: createRequest expected to set `requestId`"
104
+ );
105
+
106
+ const peer = await this.getPeer(opts?.peerId);
107
+ const stream = await this.newStream(peer);
108
+
109
+ try {
110
+ const res = await pipe(
111
+ [request.encode()],
112
+ lp.encode(),
113
+ stream,
114
+ lp.decode(),
115
+ async (source) => await all(source)
116
+ );
117
+
118
+ log("response", res);
119
+ } catch (e) {
120
+ log(
121
+ "Error subscribing to peer ",
122
+ peer.id.toString(),
123
+ "for content topics",
124
+ contentTopics,
125
+ ": ",
126
+ e
127
+ );
128
+ throw e;
129
+ }
130
+
131
+ this.addDecoders(groupedDecoders);
132
+ this.addCallback(requestId, callback);
133
+
134
+ return async () => {
135
+ await this.unsubscribe(topic, contentFilters, requestId, peer);
136
+ this.deleteDecoders(groupedDecoders);
137
+ this.deleteCallback(requestId);
138
+ };
139
+ }
140
+
141
+ private onRequest(streamData: IncomingStreamData): void {
142
+ log("Receiving message push");
143
+ try {
144
+ pipe(streamData.stream, lp.decode(), async (source) => {
145
+ for await (const bytes of source) {
146
+ const res = FilterRPC.decode(bytes.slice());
147
+ if (res.requestId && res.push?.messages?.length) {
148
+ await this.pushMessages(res.requestId, res.push.messages);
149
+ }
150
+ }
151
+ }).then(
152
+ () => {
153
+ log("Receiving pipe closed.");
154
+ },
155
+ (e) => {
156
+ log("Error with receiving pipe", e);
157
+ }
158
+ );
159
+ } catch (e) {
160
+ log("Error decoding message", e);
161
+ }
162
+ }
163
+
164
+ private async pushMessages(
165
+ requestId: string,
166
+ messages: WakuMessageProto[]
167
+ ): Promise<void> {
168
+ const callback = this.subscriptions.get(requestId);
169
+ if (!callback) {
170
+ log(`No callback registered for request ID ${requestId}`);
171
+ return;
172
+ }
173
+
174
+ for (const protoMessage of messages) {
175
+ const contentTopic = protoMessage.contentTopic;
176
+ if (!contentTopic) {
177
+ log("Message has no content topic, skipping");
178
+ return;
179
+ }
180
+
181
+ const decoders = this.decoders.get(contentTopic);
182
+ if (!decoders) {
183
+ log("No decoder for", contentTopic);
184
+ return;
185
+ }
186
+
187
+ let msg: Message | undefined;
188
+ // We don't want to wait for decoding failure, just attempt to decode
189
+ // all messages and do the call back on the one that works
190
+ // noinspection ES6MissingAwait
191
+ decoders.forEach(async (dec) => {
192
+ if (msg) return;
193
+ const decoded = await dec.fromProtoObj(toProtoMessage(protoMessage));
194
+ if (!decoded) {
195
+ log("Not able to decode message");
196
+ return;
197
+ }
198
+ // This is just to prevent more decoding attempt
199
+ // TODO: Could be better if we were to abort promises
200
+ msg = decoded;
201
+ await callback(decoded);
202
+ });
203
+ }
204
+ }
205
+
206
+ private addCallback(requestId: string, callback: Callback<any>): void {
207
+ this.subscriptions.set(requestId, callback);
208
+ }
209
+
210
+ private deleteCallback(requestId: string): void {
211
+ this.subscriptions.delete(requestId);
212
+ }
213
+
214
+ private addDecoders<T extends Message>(
215
+ decoders: Map<string, Array<Decoder<T>>>
216
+ ): void {
217
+ decoders.forEach((decoders, contentTopic) => {
218
+ const currDecs = this.decoders.get(contentTopic);
219
+ if (!currDecs) {
220
+ this.decoders.set(contentTopic, new Set(decoders));
221
+ } else {
222
+ this.decoders.set(contentTopic, new Set([...currDecs, ...decoders]));
223
+ }
224
+ });
225
+ }
226
+
227
+ private deleteDecoders<T extends Message>(
228
+ decoders: Map<string, Array<Decoder<T>>>
229
+ ): void {
230
+ decoders.forEach((decoders, contentTopic) => {
231
+ const currDecs = this.decoders.get(contentTopic);
232
+ if (currDecs) {
233
+ decoders.forEach((dec) => {
234
+ currDecs.delete(dec);
235
+ });
236
+ }
237
+ });
238
+ }
239
+
240
+ private async unsubscribe(
241
+ topic: string,
242
+ contentFilters: ContentFilter[],
243
+ requestId: string,
244
+ peer: Peer
245
+ ): Promise<void> {
246
+ const unsubscribeRequest = FilterRPC.createRequest(
247
+ topic,
248
+ contentFilters,
249
+ requestId,
250
+ false
251
+ );
252
+
253
+ const stream = await this.newStream(peer);
254
+ try {
255
+ await pipe([unsubscribeRequest.encode()], lp.encode(), stream.sink);
256
+ } catch (e) {
257
+ log("Error unsubscribing", e);
258
+ throw e;
259
+ }
260
+ }
261
+
262
+ private async newStream(peer: Peer): Promise<Stream> {
263
+ const connections = this.libp2p.connectionManager.getConnections(peer.id);
264
+ const connection = selectConnection(connections);
265
+ if (!connection) {
266
+ throw new Error("Failed to get a connection to the peer");
267
+ }
268
+
269
+ return connection.newStream(FilterCodec);
270
+ }
271
+
272
+ private async getPeer(peerId?: PeerId): Promise<Peer> {
273
+ const res = await selectPeerForProtocol(
274
+ this.libp2p.peerStore,
275
+ [FilterCodec],
276
+ peerId
277
+ );
278
+ if (!res) {
279
+ throw new Error(`Failed to select peer for ${FilterCodec}`);
280
+ }
281
+ return res.peer;
282
+ }
283
+
284
+ async peers(): Promise<Peer[]> {
285
+ return getPeersForProtocol(this.libp2p.peerStore, [FilterCodec]);
286
+ }
287
+
288
+ async randomPeer(): Promise<Peer | undefined> {
289
+ return selectRandomPeer(await this.peers());
290
+ }
291
+ }
@@ -0,0 +1,137 @@
1
+ import type { PeerId } from "@libp2p/interface-peer-id";
2
+ import type { Peer } from "@libp2p/interface-peer-store";
3
+ import type {
4
+ Encoder,
5
+ Message,
6
+ ProtocolOptions,
7
+ SendResult,
8
+ } from "@waku/interfaces";
9
+ import debug from "debug";
10
+ import all from "it-all";
11
+ import * as lp from "it-length-prefixed";
12
+ import { pipe } from "it-pipe";
13
+ import { Libp2p } from "libp2p";
14
+ import { Uint8ArrayList } from "uint8arraylist";
15
+
16
+ import { PushResponse } from "../../proto/light_push";
17
+ import { DefaultPubSubTopic } from "../constants";
18
+ import { selectConnection } from "../select_connection";
19
+ import {
20
+ getPeersForProtocol,
21
+ selectPeerForProtocol,
22
+ selectRandomPeer,
23
+ } from "../select_peer";
24
+
25
+ import { PushRPC } from "./push_rpc";
26
+
27
+ const log = debug("waku:light-push");
28
+
29
+ export const LightPushCodec = "/vac/waku/lightpush/2.0.0-beta1";
30
+ export { PushResponse };
31
+
32
+ export interface CreateOptions {
33
+ /**
34
+ * The PubSub Topic to use. Defaults to {@link DefaultPubSubTopic}.
35
+ *
36
+ * The usage of the default pubsub topic is recommended.
37
+ * See [Waku v2 Topic Usage Recommendations](https://rfc.vac.dev/spec/23/) for details.
38
+ *
39
+ * @default {@link DefaultPubSubTopic}
40
+ */
41
+ pubSubTopic?: string;
42
+ }
43
+
44
+ /**
45
+ * Implements the [Waku v2 Light Push protocol](https://rfc.vac.dev/spec/19/).
46
+ */
47
+ export class WakuLightPush {
48
+ pubSubTopic: string;
49
+
50
+ constructor(public libp2p: Libp2p, options?: CreateOptions) {
51
+ this.pubSubTopic = options?.pubSubTopic ?? DefaultPubSubTopic;
52
+ }
53
+
54
+ async push(
55
+ encoder: Encoder,
56
+ message: Partial<Message>,
57
+ opts?: ProtocolOptions
58
+ ): Promise<SendResult> {
59
+ const pubSubTopic = opts?.pubSubTopic ? opts.pubSubTopic : this.pubSubTopic;
60
+
61
+ const res = await selectPeerForProtocol(
62
+ this.libp2p.peerStore,
63
+ [LightPushCodec],
64
+ opts?.peerId
65
+ );
66
+
67
+ if (!res) {
68
+ throw new Error("Failed to get a peer");
69
+ }
70
+ const { peer } = res;
71
+
72
+ const connections = this.libp2p.connectionManager.getConnections(peer.id);
73
+ const connection = selectConnection(connections);
74
+
75
+ if (!connection) throw "Failed to get a connection to the peer";
76
+
77
+ const stream = await connection.newStream(LightPushCodec);
78
+
79
+ const recipients: PeerId[] = [];
80
+
81
+ try {
82
+ const protoMessage = await encoder.toProtoObj(message);
83
+ if (!protoMessage) {
84
+ log("Failed to encode to protoMessage, aborting push");
85
+ return { recipients };
86
+ }
87
+ const query = PushRPC.createRequest(protoMessage, pubSubTopic);
88
+ const res = await pipe(
89
+ [query.encode()],
90
+ lp.encode(),
91
+ stream,
92
+ lp.decode(),
93
+ async (source) => await all(source)
94
+ );
95
+ try {
96
+ const bytes = new Uint8ArrayList();
97
+ res.forEach((chunk) => {
98
+ bytes.append(chunk);
99
+ });
100
+
101
+ const response = PushRPC.decode(bytes).response;
102
+
103
+ if (!response) {
104
+ log("No response in PushRPC");
105
+ return { recipients };
106
+ }
107
+
108
+ if (response.isSuccess) {
109
+ recipients.push(peer.id);
110
+ }
111
+ } catch (err) {
112
+ log("Failed to decode push reply", err);
113
+ }
114
+ } catch (err) {
115
+ log("Failed to send waku light push request", err);
116
+ }
117
+ return { recipients };
118
+ }
119
+
120
+ /**
121
+ * Returns known peers from the address book (`libp2p.peerStore`) that support
122
+ * light push protocol. Waku may or may not be currently connected to these
123
+ * peers.
124
+ */
125
+ async peers(): Promise<Peer[]> {
126
+ return getPeersForProtocol(this.libp2p.peerStore, [LightPushCodec]);
127
+ }
128
+
129
+ /**
130
+ * Returns a random peer that supports light push protocol from the address
131
+ * book (`libp2p.peerStore`). Waku may or may not be currently connected to
132
+ * this peer.
133
+ */
134
+ async randomPeer(): Promise<Peer | undefined> {
135
+ return selectRandomPeer(await this.peers());
136
+ }
137
+ }
@@ -0,0 +1,39 @@
1
+ import type { Uint8ArrayList } from "uint8arraylist";
2
+ import { v4 as uuid } from "uuid";
3
+
4
+ import * as proto from "../../proto/light_push";
5
+
6
+ export class PushRPC {
7
+ public constructor(public proto: proto.PushRPC) {}
8
+
9
+ static createRequest(
10
+ message: proto.WakuMessage,
11
+ pubSubTopic: string
12
+ ): PushRPC {
13
+ return new PushRPC({
14
+ requestId: uuid(),
15
+ request: {
16
+ message: message,
17
+ pubSubTopic: pubSubTopic,
18
+ },
19
+ response: undefined,
20
+ });
21
+ }
22
+
23
+ static decode(bytes: Uint8ArrayList): PushRPC {
24
+ const res = proto.PushRPC.decode(bytes);
25
+ return new PushRPC(res);
26
+ }
27
+
28
+ encode(): Uint8Array {
29
+ return proto.PushRPC.encode(this.proto);
30
+ }
31
+
32
+ get query(): proto.PushRequest | undefined {
33
+ return this.proto.request;
34
+ }
35
+
36
+ get response(): proto.PushResponse | undefined {
37
+ return this.proto.response;
38
+ }
39
+ }
@@ -0,0 +1,10 @@
1
+ export const Symmetric = {
2
+ keySize: 32,
3
+ ivSize: 12,
4
+ tagSize: 16,
5
+ algorithm: { name: "AES-GCM", length: 128 },
6
+ };
7
+
8
+ export const Asymmetric = {
9
+ keySize: 32,
10
+ };
@@ -0,0 +1,194 @@
1
+ import * as secp from "@noble/secp256k1";
2
+
3
+ import { getSubtle, randomBytes, sha256 } from "../crypto";
4
+ import { concat, hexToBytes } from "../utils";
5
+ /**
6
+ * HKDF as implemented in go-ethereum.
7
+ */
8
+ function kdf(secret: Uint8Array, outputLength: number): Promise<Uint8Array> {
9
+ let ctr = 1;
10
+ let written = 0;
11
+ let willBeResult = Promise.resolve(new Uint8Array());
12
+ while (written < outputLength) {
13
+ const counters = new Uint8Array([ctr >> 24, ctr >> 16, ctr >> 8, ctr]);
14
+ const countersSecret = concat(
15
+ [counters, secret],
16
+ counters.length + secret.length
17
+ );
18
+ const willBeHashResult = sha256(countersSecret);
19
+ willBeResult = willBeResult.then((result) =>
20
+ willBeHashResult.then((hashResult) => {
21
+ const _hashResult = new Uint8Array(hashResult);
22
+ return concat(
23
+ [result, _hashResult],
24
+ result.length + _hashResult.length
25
+ );
26
+ })
27
+ );
28
+ written += 32;
29
+ ctr += 1;
30
+ }
31
+ return willBeResult;
32
+ }
33
+
34
+ function aesCtrEncrypt(
35
+ counter: Uint8Array,
36
+ key: ArrayBufferLike,
37
+ data: ArrayBufferLike
38
+ ): Promise<Uint8Array> {
39
+ return getSubtle()
40
+ .importKey("raw", key, "AES-CTR", false, ["encrypt"])
41
+ .then((cryptoKey) =>
42
+ getSubtle().encrypt(
43
+ { name: "AES-CTR", counter: counter, length: 128 },
44
+ cryptoKey,
45
+ data
46
+ )
47
+ )
48
+ .then((bytes) => new Uint8Array(bytes));
49
+ }
50
+
51
+ function aesCtrDecrypt(
52
+ counter: Uint8Array,
53
+ key: ArrayBufferLike,
54
+ data: ArrayBufferLike
55
+ ): Promise<Uint8Array> {
56
+ return getSubtle()
57
+ .importKey("raw", key, "AES-CTR", false, ["decrypt"])
58
+ .then((cryptoKey) =>
59
+ getSubtle().decrypt(
60
+ { name: "AES-CTR", counter: counter, length: 128 },
61
+ cryptoKey,
62
+ data
63
+ )
64
+ )
65
+ .then((bytes) => new Uint8Array(bytes));
66
+ }
67
+
68
+ function hmacSha256Sign(
69
+ key: ArrayBufferLike,
70
+ msg: ArrayBufferLike
71
+ ): PromiseLike<Uint8Array> {
72
+ const algorithm = { name: "HMAC", hash: { name: "SHA-256" } };
73
+ return getSubtle()
74
+ .importKey("raw", key, algorithm, false, ["sign"])
75
+ .then((cryptoKey) => getSubtle().sign(algorithm, cryptoKey, msg))
76
+ .then((bytes) => new Uint8Array(bytes));
77
+ }
78
+
79
+ function hmacSha256Verify(
80
+ key: ArrayBufferLike,
81
+ msg: ArrayBufferLike,
82
+ sig: ArrayBufferLike
83
+ ): Promise<boolean> {
84
+ const algorithm = { name: "HMAC", hash: { name: "SHA-256" } };
85
+ const _key = getSubtle().importKey("raw", key, algorithm, false, ["verify"]);
86
+ return _key.then((cryptoKey) =>
87
+ getSubtle().verify(algorithm, cryptoKey, sig, msg)
88
+ );
89
+ }
90
+
91
+ /**
92
+ * Derive shared secret for given private and public keys.
93
+ *
94
+ * @param privateKeyA Sender's private key (32 bytes)
95
+ * @param publicKeyB Recipient's public key (65 bytes)
96
+ * @returns A promise that resolves with the derived shared secret (Px, 32 bytes)
97
+ * @throws Error If arguments are invalid
98
+ */
99
+ function derive(privateKeyA: Uint8Array, publicKeyB: Uint8Array): Uint8Array {
100
+ if (privateKeyA.length !== 32) {
101
+ throw new Error(
102
+ `Bad private key, it should be 32 bytes but it's actually ${privateKeyA.length} bytes long`
103
+ );
104
+ } else if (publicKeyB.length !== 65) {
105
+ throw new Error(
106
+ `Bad public key, it should be 65 bytes but it's actually ${publicKeyB.length} bytes long`
107
+ );
108
+ } else if (publicKeyB[0] !== 4) {
109
+ throw new Error("Bad public key, a valid public key would begin with 4");
110
+ } else {
111
+ const px = secp.getSharedSecret(privateKeyA, publicKeyB, true);
112
+ // Remove the compression prefix
113
+ return new Uint8Array(hexToBytes(px).slice(1));
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Encrypt message for given recipient's public key.
119
+ *
120
+ * @param publicKeyTo Recipient's public key (65 bytes)
121
+ * @param msg The message being encrypted
122
+ * @return A promise that resolves with the ECIES structure serialized
123
+ */
124
+ export async function encrypt(
125
+ publicKeyTo: Uint8Array,
126
+ msg: Uint8Array
127
+ ): Promise<Uint8Array> {
128
+ const ephemPrivateKey = randomBytes(32);
129
+
130
+ const sharedPx = await derive(ephemPrivateKey, publicKeyTo);
131
+
132
+ const hash = await kdf(sharedPx, 32);
133
+
134
+ const iv = randomBytes(16);
135
+ const encryptionKey = hash.slice(0, 16);
136
+ const cipherText = await aesCtrEncrypt(iv, encryptionKey, msg);
137
+
138
+ const ivCipherText = concat([iv, cipherText], iv.length + cipherText.length);
139
+
140
+ const macKey = await sha256(hash.slice(16));
141
+ const hmac = await hmacSha256Sign(macKey, ivCipherText);
142
+ const ephemPublicKey = secp.getPublicKey(ephemPrivateKey, false);
143
+
144
+ return concat(
145
+ [ephemPublicKey, ivCipherText, hmac],
146
+ ephemPublicKey.length + ivCipherText.length + hmac.length
147
+ );
148
+ }
149
+
150
+ const metaLength = 1 + 64 + 16 + 32;
151
+
152
+ /**
153
+ * Decrypt message using given private key.
154
+ *
155
+ * @param privateKey A 32-byte private key of recipient of the message
156
+ * @param encrypted ECIES serialized structure (result of ECIES encryption)
157
+ * @returns The clear text
158
+ * @throws Error If decryption fails
159
+ */
160
+ export async function decrypt(
161
+ privateKey: Uint8Array,
162
+ encrypted: Uint8Array
163
+ ): Promise<Uint8Array> {
164
+ if (encrypted.length <= metaLength) {
165
+ throw new Error(
166
+ `Invalid Ciphertext. Data is too small. It should ba at least ${metaLength} bytes`
167
+ );
168
+ } else if (encrypted[0] !== 4) {
169
+ throw new Error(
170
+ `Not a valid ciphertext. It should begin with 4 but actually begin with ${encrypted[0]}`
171
+ );
172
+ } else {
173
+ // deserialize
174
+ const ephemPublicKey = encrypted.slice(0, 65);
175
+ const cipherTextLength = encrypted.length - metaLength;
176
+ const iv = encrypted.slice(65, 65 + 16);
177
+ const cipherAndIv = encrypted.slice(65, 65 + 16 + cipherTextLength);
178
+ const ciphertext = cipherAndIv.slice(16);
179
+ const msgMac = encrypted.slice(65 + 16 + cipherTextLength);
180
+
181
+ // check HMAC
182
+ const px = derive(privateKey, ephemPublicKey);
183
+ const hash = await kdf(px, 32);
184
+ const [encryptionKey, macKey] = await sha256(hash.slice(16)).then(
185
+ (macKey) => [hash.slice(0, 16), macKey]
186
+ );
187
+
188
+ if (!(await hmacSha256Verify(macKey, cipherAndIv, msgMac))) {
189
+ throw new Error("Incorrect MAC");
190
+ }
191
+
192
+ return aesCtrDecrypt(iv, encryptionKey, ciphertext);
193
+ }
194
+ }
@@ -0,0 +1,33 @@
1
+ import { getSubtle, randomBytes } from "../crypto";
2
+
3
+ import { Symmetric } from "./constants";
4
+
5
+ export async function encrypt(
6
+ iv: Uint8Array,
7
+ key: Uint8Array,
8
+ clearText: Uint8Array
9
+ ): Promise<Uint8Array> {
10
+ return getSubtle()
11
+ .importKey("raw", key, Symmetric.algorithm, false, ["encrypt"])
12
+ .then((cryptoKey) =>
13
+ getSubtle().encrypt({ iv, ...Symmetric.algorithm }, cryptoKey, clearText)
14
+ )
15
+ .then((cipher) => new Uint8Array(cipher));
16
+ }
17
+
18
+ export async function decrypt(
19
+ iv: Uint8Array,
20
+ key: Uint8Array,
21
+ cipherText: Uint8Array
22
+ ): Promise<Uint8Array> {
23
+ return getSubtle()
24
+ .importKey("raw", key, Symmetric.algorithm, false, ["decrypt"])
25
+ .then((cryptoKey) =>
26
+ getSubtle().decrypt({ iv, ...Symmetric.algorithm }, cryptoKey, cipherText)
27
+ )
28
+ .then((clear) => new Uint8Array(clear));
29
+ }
30
+
31
+ export function generateIv(): Uint8Array {
32
+ return randomBytes(Symmetric.ivSize);
33
+ }