nostr-double-ratchet 0.0.34 → 0.0.35
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/dist/Session.d.ts.map +1 -1
- package/dist/nostr-double-ratchet.es.js +317 -277
- package/dist/nostr-double-ratchet.umd.js +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Session.ts +35 -18
- package/src/utils.ts +35 -0
package/src/Session.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
Rumor,
|
|
11
11
|
CHAT_MESSAGE_KIND,
|
|
12
12
|
} from "./types";
|
|
13
|
-
import { kdf } from "./utils";
|
|
13
|
+
import { kdf, deepCopyState } from "./utils";
|
|
14
14
|
|
|
15
15
|
const MAX_SKIP = 1000;
|
|
16
16
|
|
|
@@ -319,47 +319,64 @@ export class Session {
|
|
|
319
319
|
throw new Error("Failed to decrypt header with current and skipped header keys");
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
+
|
|
322
323
|
private handleNostrEvent(e: { tags: string[][]; pubkey: string; content: string }) {
|
|
324
|
+
const snapshot = deepCopyState(this.state);
|
|
325
|
+
let pendingSwitch = false;
|
|
326
|
+
|
|
323
327
|
try {
|
|
324
328
|
const [header, shouldRatchet, isSkipped] = this.decryptHeader(e);
|
|
329
|
+
if (!isSkipped && this.state.theirNextNostrPublicKey !== header.nextPublicKey) {
|
|
330
|
+
this.state.theirCurrentNostrPublicKey = this.state.theirNextNostrPublicKey;
|
|
331
|
+
this.state.theirNextNostrPublicKey = header.nextPublicKey;
|
|
332
|
+
pendingSwitch = true;
|
|
333
|
+
}
|
|
325
334
|
|
|
326
335
|
if (!isSkipped) {
|
|
327
|
-
if (this.state.theirNextNostrPublicKey !== header.nextPublicKey) {
|
|
328
|
-
this.state.theirCurrentNostrPublicKey = this.state.theirNextNostrPublicKey;
|
|
329
|
-
this.state.theirNextNostrPublicKey = header.nextPublicKey;
|
|
330
|
-
this.nostrUnsubscribe?.();
|
|
331
|
-
this.nostrUnsubscribe = this.nostrNextUnsubscribe;
|
|
332
|
-
this.nostrNextUnsubscribe = this.nostrSubscribe(
|
|
333
|
-
{authors: [this.state.theirNextNostrPublicKey], kinds: [MESSAGE_EVENT_KIND]},
|
|
334
|
-
(e) => this.handleNostrEvent(e)
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
336
|
if (shouldRatchet) {
|
|
339
337
|
this.skipMessageKeys(header.previousChainLength, e.pubkey);
|
|
340
338
|
this.ratchetStep();
|
|
341
339
|
}
|
|
342
340
|
} else {
|
|
343
341
|
if (!this.state.skippedKeys[e.pubkey]?.messageKeys[header.number]) {
|
|
344
|
-
|
|
345
|
-
return
|
|
342
|
+
return;
|
|
346
343
|
}
|
|
347
344
|
}
|
|
348
345
|
|
|
349
346
|
const text = this.ratchetDecrypt(header, e.content, e.pubkey);
|
|
350
347
|
const innerEvent = JSON.parse(text);
|
|
348
|
+
|
|
351
349
|
if (!validateEvent(innerEvent)) {
|
|
350
|
+
this.state = snapshot;
|
|
352
351
|
return;
|
|
353
352
|
}
|
|
354
|
-
|
|
355
353
|
if (innerEvent.id !== getEventHash(innerEvent)) {
|
|
354
|
+
this.state = snapshot;
|
|
356
355
|
return;
|
|
357
356
|
}
|
|
358
357
|
|
|
359
|
-
|
|
358
|
+
if (pendingSwitch) {
|
|
359
|
+
this.nostrUnsubscribe?.();
|
|
360
|
+
this.nostrUnsubscribe = this.nostrNextUnsubscribe;
|
|
361
|
+
this.nostrNextUnsubscribe = this.nostrSubscribe(
|
|
362
|
+
{ authors: [this.state.theirNextNostrPublicKey], kinds: [MESSAGE_EVENT_KIND] },
|
|
363
|
+
(ev) => this.handleNostrEvent(ev)
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
this.internalSubscriptions.forEach(callback => callback(innerEvent, e as VerifiedEvent));
|
|
360
368
|
} catch (error) {
|
|
361
|
-
|
|
362
|
-
|
|
369
|
+
this.state = snapshot;
|
|
370
|
+
if (error instanceof Error) {
|
|
371
|
+
if (error.message.includes("Failed to decrypt header")) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (error.message === "invalid MAC") {
|
|
376
|
+
// Duplicate or stale ciphertexts can hit decrypt() again after a state restore.
|
|
377
|
+
// nip44 throws "invalid MAC" in that case, but the message has already been handled.
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
363
380
|
}
|
|
364
381
|
throw error;
|
|
365
382
|
}
|
package/src/utils.ts
CHANGED
|
@@ -115,6 +115,41 @@ export function deserializeSessionState(data: string): SessionState {
|
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
export function deepCopyState(s: SessionState): SessionState {
|
|
119
|
+
return {
|
|
120
|
+
rootKey: new Uint8Array(s.rootKey),
|
|
121
|
+
theirCurrentNostrPublicKey: s.theirCurrentNostrPublicKey,
|
|
122
|
+
theirNextNostrPublicKey: s.theirNextNostrPublicKey,
|
|
123
|
+
ourCurrentNostrKey: s.ourCurrentNostrKey
|
|
124
|
+
? {
|
|
125
|
+
publicKey: s.ourCurrentNostrKey.publicKey,
|
|
126
|
+
privateKey: new Uint8Array(s.ourCurrentNostrKey.privateKey),
|
|
127
|
+
}
|
|
128
|
+
: undefined,
|
|
129
|
+
ourNextNostrKey: {
|
|
130
|
+
publicKey: s.ourNextNostrKey.publicKey,
|
|
131
|
+
privateKey: new Uint8Array(s.ourNextNostrKey.privateKey),
|
|
132
|
+
},
|
|
133
|
+
receivingChainKey: s.receivingChainKey ? new Uint8Array(s.receivingChainKey) : undefined,
|
|
134
|
+
sendingChainKey: s.sendingChainKey ? new Uint8Array(s.sendingChainKey) : undefined,
|
|
135
|
+
sendingChainMessageNumber: s.sendingChainMessageNumber,
|
|
136
|
+
receivingChainMessageNumber: s.receivingChainMessageNumber,
|
|
137
|
+
previousSendingChainMessageCount: s.previousSendingChainMessageCount,
|
|
138
|
+
skippedKeys: Object.fromEntries(
|
|
139
|
+
Object.entries(s.skippedKeys).map(([author, entry]: any) => [
|
|
140
|
+
author,
|
|
141
|
+
{
|
|
142
|
+
headerKeys: entry.headerKeys.map((hk: Uint8Array) => new Uint8Array(hk)),
|
|
143
|
+
messageKeys: Object.fromEntries(
|
|
144
|
+
Object.entries(entry.messageKeys).map(([n, mk]: any) => [n, new Uint8Array(mk)])
|
|
145
|
+
),
|
|
146
|
+
},
|
|
147
|
+
])
|
|
148
|
+
),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
118
153
|
export async function* createEventStream(session: Session): AsyncGenerator<Rumor, void, unknown> {
|
|
119
154
|
const messageQueue: Rumor[] = [];
|
|
120
155
|
let resolveNext: ((_value: Rumor) => void) | null = null;
|