-
Notifications
You must be signed in to change notification settings - Fork 18
/
CreateAndSendOverProxy.test.ts
330 lines (285 loc) · 11.8 KB
/
CreateAndSendOverProxy.test.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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
import {
createDeliveryServiceProfile,
createProfile,
} from '@dm3-org/dm3-lib-profile';
import { ethers } from 'ethers';
import { createJsonDataUri } from '@dm3-org/dm3-lib-shared';
import {
EncryptionEnvelop,
createJsonRpcCallSubmitMessage,
createMessage,
JsonRpcRequest,
handleMessageOnDeliveryService,
decryptEnvelop,
checkMessageSignature,
sendOverMessageProxy,
createProxyEnvelop,
ProxyEnvelop,
} from '@dm3-org/dm3-lib-messaging';
describe('Profile creation and sending a message using a message proxy', () => {
let provider: ethers.providers.JsonRpcProvider;
let aliceWallet: ethers.Wallet;
let bobWallet: ethers.Wallet;
let signer: Record<string, ethers.Wallet>;
let textRecordMockReg: Record<string, Record<string, string>>;
let httpPostRequests: Record<string, JsonRpcRequest<string>[]>;
let httpProxyPostRequests: Record<string, ProxyEnvelop[]>;
let pushReg: Record<string, (envelop: EncryptionEnvelop) => Promise<void>>;
const textRecordPublishMock = (
ensName: string,
recordName: string,
text: string,
) => {
if (!textRecordMockReg[ensName]) {
textRecordMockReg[ensName] = {};
}
textRecordMockReg[ensName][recordName] = text;
};
const httpServerPostMock = (url: string, body: JsonRpcRequest<string>) => {
if (!httpPostRequests[url]) {
httpPostRequests[url] = [];
}
httpPostRequests[url].push(body);
};
const httpProxyServerPostMock = (url: string, body: ProxyEnvelop) => {
if (!httpProxyPostRequests[url]) {
httpProxyPostRequests[url] = [];
}
httpProxyPostRequests[url].push(body);
};
const getMessageFromProxyMock = (url: string) => {
return httpProxyPostRequests[url].map((req) => req);
};
const getMessagesMock = (url: string) => {
return httpPostRequests[url].map((req) => req.params[0]);
};
const onMessageReg = (
ensName: string,
cb: (envelop: EncryptionEnvelop) => Promise<void>,
) => {
pushReg[ensName] = cb;
};
const pushMessage = (ensName: string, envelop: EncryptionEnvelop) => {
pushReg[ensName](envelop);
};
beforeEach(() => {
pushReg = {};
textRecordMockReg = {};
httpPostRequests = {};
httpProxyPostRequests = {};
aliceWallet = new ethers.Wallet(
'0x6213babbc500db267990a92da63a375350f942705d00a4509b7eb6ce88005bb7',
);
bobWallet = new ethers.Wallet(
'0xb71d06f166a1fde4b7158b26c4d8940dfb2b7510808fd1d21157831b67286ed1',
);
signer = {};
signer[aliceWallet.address] = aliceWallet;
signer[bobWallet.address] = bobWallet;
const nameMapping: Record<string, string> = {
'alice.eth': aliceWallet.address,
'bob.eth': bobWallet.address,
};
const newProvider = new ethers.providers.JsonRpcProvider(
'http://localhost',
);
provider = {
...newProvider,
send: (methode: string, params: any[]) => {
if (methode === 'personal_sign') {
const wallet = signer[params[1]];
if (!wallet) {
throw Error('Unknown signer');
}
return wallet.signMessage(params[0]);
} else {
return provider.send(methode, params);
}
},
resolveName: async (name: string) => {
return nameMapping[name];
},
getResolver: ((name: string) =>
textRecordMockReg[name]
? {
getText: async (recordName: string) =>
textRecordMockReg[name][recordName],
}
: null) as any,
} as ethers.providers.JsonRpcProvider;
});
it('should send a message from Alice to Bob', async () => {
// Function to get HTTP Ressoucres, only needed for IPFS or HTTP profiles (deprecated)
// Use CCIP instead of IPFS or HTTP profiles
const getRessource = async (uri: string) => null as any;
// ------
//
// Delivery services setup
//
// ------
// The following function calls returns the profile, the keys,
// and the nonce for delivery services A and B
// 'http://a' and 'http://b' are the URLs pointing to the delivery service endpoint
const { deliveryServiceProfile: deliveryServiceProfileA } =
await createDeliveryServiceProfile('http://a');
const {
deliveryServiceProfile: deliveryServiceProfileB,
keys: dsKeysB,
} = await createDeliveryServiceProfile('http://b');
// The delivery service profiles need to be transformed into a data URI
// before they can be published on-chain
const profileJsonDataUriA = createJsonDataUri(deliveryServiceProfileA);
const profileJsonDataUriB = createJsonDataUri(deliveryServiceProfileB);
// The profiles must be published on-chain as ENS 'network.dm3.deliveryService' text record.
// In this case, a text record mock is used instead of setting
// the actual 'network.dm3.deliveryService' text record of a.eth and b.eth
textRecordPublishMock(
'a.eth',
'network.dm3.deliveryService',
profileJsonDataUriA,
);
textRecordPublishMock(
'b.eth',
'network.dm3.deliveryService',
profileJsonDataUriB,
);
// ------
//
// Creating and publishing user profiles
//
// ------
// Every dm3 user needs to create and publish a profile containing:
// - the public signing key,
// - the public encryption key,
// - and a list of the ENS names referencing the delivery service the user subscribed to
// The following function calls will return the user profile, the keys, and the nonce.
const { signedProfile: aliceProfile, keys: aliceKeys } =
await createProfile(aliceWallet.address, ['a.eth'], provider);
const { signedProfile: bobProfile, keys: bobKeys } =
await createProfile(bobWallet.address, ['b.eth'], provider);
// The user profiles need to be transformed into a data URI
// before they can be published on-chain
const profileJsonDataUriAlice = createJsonDataUri(aliceProfile);
const profileJsonDataUriBob = createJsonDataUri(bobProfile);
// The profiles must be published on-chain or made available via CCIP.
// Therefore the ENS 'network.dm3.profile' text record needs to be set to the data URI
// containing the user profile.
// In this case a text record mock is used instead of setting
// the actual 'network.dm3.deliveryService' text record of alice.eth and bob.eth
textRecordPublishMock(
'alice.eth',
'network.dm3.profile',
profileJsonDataUriAlice,
);
textRecordPublishMock(
'bob.eth',
'network.dm3.profile',
profileJsonDataUriBob,
);
// ------
//
// Sending and receiving messages
// [ Sender Client ] -> Message Proxy -> Receiver Delivery Service -> Receiver Client
//
// ------
// To receive messages the dm3 clients can subscribe to a "new message" event
// via a WebSocket that is provided by the delivery service.
// The messages could also be fetched via a REST request.
// In this example Alice and Bob subscribe to a mocked push service
onMessageReg('alice.eth', async (envelop: EncryptionEnvelop) => {});
onMessageReg('bob.eth', async (encryptedEnvelop: EncryptionEnvelop) => {
// ------
//
// Message Processing on the receiver client
// Sender Client -> Message Proxy -> Receiver Delivery Service -> [ Receiver Client ]
//
// ------
// The client will decrypt a received message
const envelop = await decryptEnvelop(
encryptedEnvelop,
bobKeys.encryptionKeyPair,
);
expect(envelop.message.message).toStrictEqual('Test message');
// The client must check the signature of a received message
const validMessage = await checkMessageSignature(
envelop.message,
aliceProfile.profile.publicSigningKey,
'alice.eth',
);
expect(validMessage).toStrictEqual(true);
});
// The following lines will create a message with the content 'Test Message'
const messageAliceToBob = await createMessage(
'bob.eth',
'alice.eth',
'Test message',
aliceKeys.signingKeyPair.privateKey,
);
// The message must be encrypted and put into an envelope containing
// meta information needed for the delivery
// The createEnvelop function will return an encrypted and unencrypted version of the envelope.
// The unencrypted version could be used for storing it
// with a chunk of other received and send messages in a message store.
// The message store could be encrypted with the generated symmetrical key.
// The client needs to create an envelope for every delivery service
// in the receivers delivery service list.
const proxyEnvelop = await createProxyEnvelop(
messageAliceToBob,
provider,
aliceKeys,
getRessource,
);
httpProxyServerPostMock('http://proxy', proxyEnvelop);
// ------
//
// Message Processing on the proxy delivery service
// Sender Client -> [ Message Proxy ] -> Receiver Delivery Service -> Receiver Client
//
// ------
await Promise.all(
// Proxy goes over the list of envelops and tries to send the message to the specified delivery service.
// The proxy stops after the first success
getMessageFromProxyMock('http://proxy').map(async (envelop) => {
await sendOverMessageProxy({
getRessource,
provider,
proxyEnvelop: envelop,
submitMessage: async (url, envelop) => {
httpServerPostMock(
url,
createJsonRpcCallSubmitMessage(envelop, ''),
);
},
});
}),
);
// ------
//
// Message Processing on the delivery service
// Sender Client -> Message Proxy -> [ Receiver Delivery Service ] -> Receiver Client
//
// ------
const messagesOnDeliveryService = getMessagesMock(
deliveryServiceProfileB.url,
);
// handleMessageOnDeliveryService() will decrypt the delivery information
// and add a postmark (incoming timestamp) to the envelop
const processedMessages = await Promise.all(
messagesOnDeliveryService.map(
async (encryptedEnvelop) =>
await handleMessageOnDeliveryService(
JSON.parse(encryptedEnvelop),
dsKeysB,
bobProfile.profile,
),
),
);
// After processing the envelope, the delivery service forwards the message to the receiver
processedMessages.forEach((envelopContainer) =>
pushMessage(
envelopContainer.decryptedDeliveryInformation.to,
envelopContainer.encryptedEnvelop,
),
);
});
});