@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.
- package/CHANGELOG.md +614 -0
- package/README.md +56 -0
- package/bundle/browser-1e1a2f27.js +722 -0
- package/bundle/crypto-8551d579.js +2585 -0
- package/bundle/crypto-b00764b7.js +1772 -0
- package/bundle/enr-564d4a51.js +20785 -0
- package/bundle/enr-9fc5eed8.js +20786 -0
- package/bundle/enr-f6e82a53.js +20785 -0
- package/bundle/events-158407bb.js +1929 -0
- package/bundle/events-fcbda4dc.js +76 -0
- package/bundle/index-02d21809.js +20 -0
- package/bundle/index-0a4bdddc.js +2976 -0
- package/bundle/index-2ae915be.js +1854 -0
- package/bundle/index-64ce43f0.js +69 -0
- package/bundle/index-691c0be6.js +4059 -0
- package/bundle/index-a013a259.js +20 -0
- package/bundle/index-ba42b4fc.js +862 -0
- package/bundle/index.js +13428 -0
- package/bundle/lib/enr.js +8 -0
- package/bundle/lib/peer_discovery_dns.js +5018 -0
- package/bundle/lib/peer_discovery_static_list.js +75 -0
- package/bundle/lib/predefined_bootstrap_nodes.js +59 -0
- package/bundle/lib/utils.js +1 -0
- package/bundle/lib/wait_for_remote_peer.js +327 -0
- package/bundle/lib/waku_message/topic_only_message.js +4 -0
- package/bundle/lib/waku_message/version_0.js +4 -0
- package/bundle/lib/waku_message/version_1.js +463 -0
- package/bundle/message-e2db79d7.js +8393 -0
- package/bundle/multiaddr_to_peer_info-c406b1e1.js +19 -0
- package/bundle/multiaddr_to_peer_info-fd1de516.js +19 -0
- package/bundle/random_subset-75d1c511.js +26 -0
- package/bundle/topic_only_message-34f36fa6.js +82 -0
- package/bundle/utils-9a3221f2.js +815 -0
- package/bundle/version_0-e6fe440c.js +317 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/constants.d.ts +4 -0
- package/dist/lib/constants.js +5 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/crypto.d.ts +34 -0
- package/dist/lib/crypto.js +79 -0
- package/dist/lib/crypto.js.map +1 -0
- package/dist/lib/enr/constants.d.ts +4 -0
- package/dist/lib/enr/constants.js +8 -0
- package/dist/lib/enr/constants.js.map +1 -0
- package/dist/lib/enr/enr.d.ts +90 -0
- package/dist/lib/enr/enr.js +432 -0
- package/dist/lib/enr/enr.js.map +1 -0
- package/dist/lib/enr/index.d.ts +5 -0
- package/dist/lib/enr/index.js +6 -0
- package/dist/lib/enr/index.js.map +1 -0
- package/dist/lib/enr/keypair/index.d.ts +8 -0
- package/dist/lib/enr/keypair/index.js +53 -0
- package/dist/lib/enr/keypair/index.js.map +1 -0
- package/dist/lib/enr/keypair/secp256k1.d.ts +13 -0
- package/dist/lib/enr/keypair/secp256k1.js +57 -0
- package/dist/lib/enr/keypair/secp256k1.js.map +1 -0
- package/dist/lib/enr/keypair/types.d.ts +13 -0
- package/dist/lib/enr/keypair/types.js +7 -0
- package/dist/lib/enr/keypair/types.js.map +1 -0
- package/dist/lib/enr/multiaddr_from_fields.d.ts +2 -0
- package/dist/lib/enr/multiaddr_from_fields.js +8 -0
- package/dist/lib/enr/multiaddr_from_fields.js.map +1 -0
- package/dist/lib/enr/multiaddrs_codec.d.ts +3 -0
- package/dist/lib/enr/multiaddrs_codec.js +32 -0
- package/dist/lib/enr/multiaddrs_codec.js.map +1 -0
- package/dist/lib/enr/types.d.ts +8 -0
- package/dist/lib/enr/types.js +3 -0
- package/dist/lib/enr/types.js.map +1 -0
- package/dist/lib/enr/v4.d.ts +3 -0
- package/dist/lib/enr/v4.js +14 -0
- package/dist/lib/enr/v4.js.map +1 -0
- package/dist/lib/enr/waku2_codec.d.ts +8 -0
- package/dist/lib/enr/waku2_codec.js +36 -0
- package/dist/lib/enr/waku2_codec.js.map +1 -0
- package/dist/lib/group_by.d.ts +3 -0
- package/dist/lib/group_by.js +13 -0
- package/dist/lib/group_by.js.map +1 -0
- package/dist/lib/multiaddr_to_peer_info.d.ts +3 -0
- package/dist/lib/multiaddr_to_peer_info.js +15 -0
- package/dist/lib/multiaddr_to_peer_info.js.map +1 -0
- package/dist/lib/peer_discovery_dns/dns.d.ts +48 -0
- package/dist/lib/peer_discovery_dns/dns.js +158 -0
- package/dist/lib/peer_discovery_dns/dns.js.map +1 -0
- package/dist/lib/peer_discovery_dns/dns_over_https.d.ts +32 -0
- package/dist/lib/peer_discovery_dns/dns_over_https.js +87 -0
- package/dist/lib/peer_discovery_dns/dns_over_https.js.map +1 -0
- package/dist/lib/peer_discovery_dns/enrtree.d.ts +33 -0
- package/dist/lib/peer_discovery_dns/enrtree.js +76 -0
- package/dist/lib/peer_discovery_dns/enrtree.js.map +1 -0
- package/dist/lib/peer_discovery_dns/fetch_nodes.d.ts +14 -0
- package/dist/lib/peer_discovery_dns/fetch_nodes.js +133 -0
- package/dist/lib/peer_discovery_dns/fetch_nodes.js.map +1 -0
- package/dist/lib/peer_discovery_dns/index.d.ts +30 -0
- package/dist/lib/peer_discovery_dns/index.js +54 -0
- package/dist/lib/peer_discovery_dns/index.js.map +1 -0
- package/dist/lib/peer_discovery_static_list.d.ts +44 -0
- package/dist/lib/peer_discovery_static_list.js +72 -0
- package/dist/lib/peer_discovery_static_list.js.map +1 -0
- package/dist/lib/predefined_bootstrap_nodes.d.ts +35 -0
- package/dist/lib/predefined_bootstrap_nodes.js +56 -0
- package/dist/lib/predefined_bootstrap_nodes.js.map +1 -0
- package/dist/lib/push_or_init_map.d.ts +1 -0
- package/dist/lib/push_or_init_map.js +9 -0
- package/dist/lib/push_or_init_map.js.map +1 -0
- package/dist/lib/random_subset.d.ts +4 -0
- package/dist/lib/random_subset.js +25 -0
- package/dist/lib/random_subset.js.map +1 -0
- package/dist/lib/select_connection.d.ts +2 -0
- package/dist/lib/select_connection.js +19 -0
- package/dist/lib/select_connection.js.map +1 -0
- package/dist/lib/select_peer.d.ts +15 -0
- package/dist/lib/select_peer.js +59 -0
- package/dist/lib/select_peer.js.map +1 -0
- package/dist/lib/to_proto_message.d.ts +3 -0
- package/dist/lib/to_proto_message.js +11 -0
- package/dist/lib/to_proto_message.js.map +1 -0
- package/dist/lib/utils.d.ts +22 -0
- package/dist/lib/utils.js +40 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/lib/wait_for_remote_peer.d.ts +22 -0
- package/dist/lib/wait_for_remote_peer.js +113 -0
- package/dist/lib/wait_for_remote_peer.js.map +1 -0
- package/dist/lib/waku.d.ts +61 -0
- package/dist/lib/waku.js +174 -0
- package/dist/lib/waku.js.map +1 -0
- package/dist/lib/waku_filter/filter_rpc.d.ts +25 -0
- package/dist/lib/waku_filter/filter_rpc.js +44 -0
- package/dist/lib/waku_filter/filter_rpc.js.map +1 -0
- package/dist/lib/waku_filter/index.d.ts +50 -0
- package/dist/lib/waku_filter/index.js +181 -0
- package/dist/lib/waku_filter/index.js.map +1 -0
- package/dist/lib/waku_light_push/index.d.ts +38 -0
- package/dist/lib/waku_light_push/index.js +83 -0
- package/dist/lib/waku_light_push/index.js.map +1 -0
- package/dist/lib/waku_light_push/push_rpc.d.ts +11 -0
- package/dist/lib/waku_light_push/push_rpc.js +31 -0
- package/dist/lib/waku_light_push/push_rpc.js.map +1 -0
- package/dist/lib/waku_message/constants.d.ts +12 -0
- package/dist/lib/waku_message/constants.js +10 -0
- package/dist/lib/waku_message/constants.js.map +1 -0
- package/dist/lib/waku_message/ecies.d.ts +17 -0
- package/dist/lib/waku_message/ecies.js +126 -0
- package/dist/lib/waku_message/ecies.js.map +1 -0
- package/dist/lib/waku_message/symmetric.d.ts +3 -0
- package/dist/lib/waku_message/symmetric.js +18 -0
- package/dist/lib/waku_message/symmetric.js.map +1 -0
- package/dist/lib/waku_message/topic_only_message.d.ts +15 -0
- package/dist/lib/waku_message/topic_only_message.js +31 -0
- package/dist/lib/waku_message/topic_only_message.js.map +1 -0
- package/dist/lib/waku_message/version_0.d.ts +26 -0
- package/dist/lib/waku_message/version_0.js +96 -0
- package/dist/lib/waku_message/version_0.js.map +1 -0
- package/dist/lib/waku_message/version_1.d.ts +93 -0
- package/dist/lib/waku_message/version_1.js +325 -0
- package/dist/lib/waku_message/version_1.js.map +1 -0
- package/dist/lib/waku_relay/constants.d.ts +63 -0
- package/dist/lib/waku_relay/constants.js +67 -0
- package/dist/lib/waku_relay/constants.js.map +1 -0
- package/dist/lib/waku_relay/index.d.ts +65 -0
- package/dist/lib/waku_relay/index.js +111 -0
- package/dist/lib/waku_relay/index.js.map +1 -0
- package/dist/lib/waku_store/history_rpc.d.ts +27 -0
- package/dist/lib/waku_store/history_rpc.js +71 -0
- package/dist/lib/waku_store/history_rpc.js.map +1 -0
- package/dist/lib/waku_store/index.d.ts +126 -0
- package/dist/lib/waku_store/index.js +218 -0
- package/dist/lib/waku_store/index.js.map +1 -0
- package/dist/proto/filter.d.ts +65 -0
- package/dist/proto/filter.js +425 -0
- package/dist/proto/filter.js.map +1 -0
- package/dist/proto/light_push.d.ts +57 -0
- package/dist/proto/light_push.js +369 -0
- package/dist/proto/light_push.js.map +1 -0
- package/dist/proto/message.d.ts +29 -0
- package/dist/proto/message.js +215 -0
- package/dist/proto/message.js.map +1 -0
- package/dist/proto/store.d.ts +104 -0
- package/dist/proto/store.js +602 -0
- package/dist/proto/store.js.map +1 -0
- package/dist/proto/topic_only_message.d.ts +10 -0
- package/dist/proto/topic_only_message.js +46 -0
- package/dist/proto/topic_only_message.js.map +1 -0
- package/package.json +292 -0
- package/src/index.ts +33 -0
- package/src/lib/constants.ts +4 -0
- package/src/lib/crypto.ts +100 -0
- package/src/lib/enr/constants.ts +10 -0
- package/src/lib/enr/enr.ts +516 -0
- package/src/lib/enr/index.ts +5 -0
- package/src/lib/enr/keypair/index.ts +76 -0
- package/src/lib/enr/keypair/secp256k1.ts +69 -0
- package/src/lib/enr/keypair/types.ts +14 -0
- package/src/lib/enr/multiaddr_from_fields.ts +18 -0
- package/src/lib/enr/multiaddrs_codec.ts +50 -0
- package/src/lib/enr/types.ts +11 -0
- package/src/lib/enr/v4.ts +22 -0
- package/src/lib/enr/waku2_codec.ts +39 -0
- package/src/lib/group_by.ts +14 -0
- package/src/lib/multiaddr_to_peer_info.ts +17 -0
- package/src/lib/peer_discovery_dns/dns.ts +223 -0
- package/src/lib/peer_discovery_dns/dns_over_https.ts +98 -0
- package/src/lib/peer_discovery_dns/enrtree.ts +123 -0
- package/src/lib/peer_discovery_dns/fetch_nodes.ts +180 -0
- package/src/lib/peer_discovery_dns/index.ts +84 -0
- package/src/lib/peer_discovery_static_list.ts +118 -0
- package/src/lib/predefined_bootstrap_nodes.ts +72 -0
- package/src/lib/push_or_init_map.ts +13 -0
- package/src/lib/random_subset.ts +30 -0
- package/src/lib/select_connection.ts +24 -0
- package/src/lib/select_peer.ts +77 -0
- package/src/lib/to_proto_message.ts +15 -0
- package/src/lib/utils.ts +50 -0
- package/src/lib/wait_for_remote_peer.ts +151 -0
- package/src/lib/waku.ts +258 -0
- package/src/lib/waku_filter/filter_rpc.ts +57 -0
- package/src/lib/waku_filter/index.ts +291 -0
- package/src/lib/waku_light_push/index.ts +137 -0
- package/src/lib/waku_light_push/push_rpc.ts +39 -0
- package/src/lib/waku_message/constants.ts +10 -0
- package/src/lib/waku_message/ecies.ts +194 -0
- package/src/lib/waku_message/symmetric.ts +33 -0
- package/src/lib/waku_message/topic_only_message.ts +40 -0
- package/src/lib/waku_message/version_0.ts +121 -0
- package/src/lib/waku_message/version_1.ts +457 -0
- package/src/lib/waku_relay/constants.ts +77 -0
- package/src/lib/waku_relay/index.ts +189 -0
- package/src/lib/waku_store/history_rpc.ts +94 -0
- package/src/lib/waku_store/index.ts +372 -0
- package/src/proto/filter.ts +602 -0
- package/src/proto/light_push.ts +526 -0
- package/src/proto/message.ts +304 -0
- package/src/proto/store.ts +844 -0
- 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,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
|
+
}
|