@voidly/agent-sdk 3.2.5 → 3.2.7
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/index.js +41 -20
- package/dist/index.mjs +41 -20
- package/examples/post-quantum.mjs +99 -0
- package/examples/sse-streaming.mjs +52 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4362,14 +4362,16 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
4362
4362
|
method: "PUT",
|
|
4363
4363
|
headers: { "Content-Type": "application/json", "X-Agent-Key": this.apiKey },
|
|
4364
4364
|
body: JSON.stringify({ value: blob })
|
|
4365
|
-
}).catch(() => {
|
|
4365
|
+
}).catch((e) => {
|
|
4366
|
+
console.warn(`[voidly] \u26A0 Relay ratchet persist failed: ${e instanceof Error ? e.message : e}`);
|
|
4366
4367
|
});
|
|
4367
4368
|
break;
|
|
4368
4369
|
case "custom":
|
|
4369
4370
|
if (this._onPersist) await this._onPersist(blob);
|
|
4370
4371
|
break;
|
|
4371
4372
|
}
|
|
4372
|
-
} catch {
|
|
4373
|
+
} catch (e) {
|
|
4374
|
+
console.warn(`[voidly] \u26A0 Ratchet state persistence failed (${this._persistMode}): ${e instanceof Error ? e.message : e}`);
|
|
4373
4375
|
}
|
|
4374
4376
|
}
|
|
4375
4377
|
/** Load persisted ratchet state and restore into memory */
|
|
@@ -4404,14 +4406,16 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
4404
4406
|
const data = await res.json();
|
|
4405
4407
|
blob = data.value;
|
|
4406
4408
|
}
|
|
4407
|
-
} catch {
|
|
4409
|
+
} catch (e) {
|
|
4410
|
+
console.warn(`[voidly] \u26A0 Relay ratchet load failed: ${e instanceof Error ? e.message : e}`);
|
|
4408
4411
|
}
|
|
4409
4412
|
break;
|
|
4410
4413
|
case "custom":
|
|
4411
4414
|
if (this._onLoad) blob = await this._onLoad();
|
|
4412
4415
|
break;
|
|
4413
4416
|
}
|
|
4414
|
-
} catch {
|
|
4417
|
+
} catch (e) {
|
|
4418
|
+
console.warn(`[voidly] \u26A0 Ratchet state load failed (${this._persistMode}): ${e instanceof Error ? e.message : e}`);
|
|
4415
4419
|
return;
|
|
4416
4420
|
}
|
|
4417
4421
|
if (!blob) return;
|
|
@@ -4423,6 +4427,12 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
4423
4427
|
const states = JSON.parse((0, import_tweetnacl_util.encodeUTF8)(decrypted));
|
|
4424
4428
|
for (const [pairId, rs] of Object.entries(states)) {
|
|
4425
4429
|
if (this._ratchetStates.has(pairId)) continue;
|
|
4430
|
+
if (!rs || typeof rs.sendStep !== "number" || typeof rs.recvStep !== "number" || typeof rs.sendChainKey !== "string" || typeof rs.recvChainKey !== "string") {
|
|
4431
|
+
continue;
|
|
4432
|
+
}
|
|
4433
|
+
if (rs.sendStep < 0 || rs.recvStep < 0 || rs.sendStep > 4294967295 || rs.recvStep > 4294967295) {
|
|
4434
|
+
continue;
|
|
4435
|
+
}
|
|
4426
4436
|
const state = {
|
|
4427
4437
|
sendChainKey: (0, import_tweetnacl_util.decodeBase64)(rs.sendChainKey),
|
|
4428
4438
|
sendStep: rs.sendStep,
|
|
@@ -4430,20 +4440,21 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
4430
4440
|
recvStep: rs.recvStep,
|
|
4431
4441
|
skippedKeys: /* @__PURE__ */ new Map()
|
|
4432
4442
|
};
|
|
4433
|
-
if (rs.rootKey) state.rootKey = (0, import_tweetnacl_util.decodeBase64)(rs.rootKey);
|
|
4434
|
-
if (rs.dhSendSecretKey && rs.dhSendPublicKey) {
|
|
4443
|
+
if (rs.rootKey && typeof rs.rootKey === "string") state.rootKey = (0, import_tweetnacl_util.decodeBase64)(rs.rootKey);
|
|
4444
|
+
if (typeof rs.dhSendSecretKey === "string" && typeof rs.dhSendPublicKey === "string") {
|
|
4435
4445
|
state.dhSendKeyPair = {
|
|
4436
4446
|
secretKey: (0, import_tweetnacl_util.decodeBase64)(rs.dhSendSecretKey),
|
|
4437
4447
|
publicKey: (0, import_tweetnacl_util.decodeBase64)(rs.dhSendPublicKey)
|
|
4438
4448
|
};
|
|
4439
4449
|
}
|
|
4440
|
-
if (rs.dhRecvPubKey) state.dhRecvPubKey = (0, import_tweetnacl_util.decodeBase64)(rs.dhRecvPubKey);
|
|
4441
|
-
if (rs.prevSendStep
|
|
4450
|
+
if (typeof rs.dhRecvPubKey === "string") state.dhRecvPubKey = (0, import_tweetnacl_util.decodeBase64)(rs.dhRecvPubKey);
|
|
4451
|
+
if (typeof rs.prevSendStep === "number" && rs.prevSendStep >= 0) state.prevSendStep = rs.prevSendStep;
|
|
4442
4452
|
if (state.sendChainKey.length === 32 && state.recvChainKey.length === 32) {
|
|
4443
4453
|
this._ratchetStates.set(pairId, state);
|
|
4444
4454
|
}
|
|
4445
4455
|
}
|
|
4446
|
-
} catch {
|
|
4456
|
+
} catch (e) {
|
|
4457
|
+
console.warn(`[voidly] \u26A0 Ratchet state restore failed (corrupt data, starting fresh): ${e instanceof Error ? e.message : e}`);
|
|
4447
4458
|
}
|
|
4448
4459
|
}
|
|
4449
4460
|
/** IndexedDB put helper (browser only) */
|
|
@@ -4768,16 +4779,16 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
4768
4779
|
if (msg.envelope) {
|
|
4769
4780
|
try {
|
|
4770
4781
|
const env = JSON.parse(msg.envelope);
|
|
4771
|
-
if (typeof env.ratchet_step === "number") {
|
|
4782
|
+
if (typeof env.ratchet_step === "number" && Number.isInteger(env.ratchet_step) && env.ratchet_step >= 0 && env.ratchet_step <= 4294967295) {
|
|
4772
4783
|
envelopeRatchetStep = env.ratchet_step;
|
|
4773
4784
|
}
|
|
4774
|
-
if (typeof env.pq_ciphertext === "string") {
|
|
4785
|
+
if (typeof env.pq_ciphertext === "string" && env.pq_ciphertext.length <= 65536) {
|
|
4775
4786
|
envelopePqCiphertext = env.pq_ciphertext;
|
|
4776
4787
|
}
|
|
4777
|
-
if (typeof env.dh_ratchet_key === "string") {
|
|
4788
|
+
if (typeof env.dh_ratchet_key === "string" && env.dh_ratchet_key.length <= 256) {
|
|
4778
4789
|
envelopeDhRatchetKey = env.dh_ratchet_key;
|
|
4779
4790
|
}
|
|
4780
|
-
if (typeof env.pn === "number") {
|
|
4791
|
+
if (typeof env.pn === "number" && Number.isInteger(env.pn) && env.pn >= 0 && env.pn <= 4294967295) {
|
|
4781
4792
|
envelopePn = env.pn;
|
|
4782
4793
|
}
|
|
4783
4794
|
} catch {
|
|
@@ -4842,9 +4853,10 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
4842
4853
|
if (!state.dhSkippedKeys) state.dhSkippedKeys = /* @__PURE__ */ new Map();
|
|
4843
4854
|
state.dhSkippedKeys.set(skipKey, skippedMk);
|
|
4844
4855
|
ck = nextChainKey;
|
|
4845
|
-
|
|
4856
|
+
while (state.dhSkippedKeys.size > MAX_SKIP) {
|
|
4846
4857
|
const oldest = state.dhSkippedKeys.keys().next().value;
|
|
4847
4858
|
if (oldest !== void 0) state.dhSkippedKeys.delete(oldest);
|
|
4859
|
+
else break;
|
|
4848
4860
|
}
|
|
4849
4861
|
}
|
|
4850
4862
|
}
|
|
@@ -4877,16 +4889,18 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
4877
4889
|
} else if (targetStep > state.recvStep) {
|
|
4878
4890
|
const skip = targetStep - state.recvStep;
|
|
4879
4891
|
if (skip > MAX_SKIP) {
|
|
4880
|
-
|
|
4892
|
+
this._decryptFailCount++;
|
|
4893
|
+
continue;
|
|
4881
4894
|
} else {
|
|
4882
4895
|
let ck = state.recvChainKey;
|
|
4883
4896
|
for (let i = state.recvStep + 1; i < targetStep; i++) {
|
|
4884
4897
|
const { nextChainKey: nextChainKey2, messageKey: skippedMk } = await ratchetStep(ck);
|
|
4885
4898
|
state.skippedKeys.set(i, skippedMk);
|
|
4886
4899
|
ck = nextChainKey2;
|
|
4887
|
-
|
|
4900
|
+
while (state.skippedKeys.size > MAX_SKIP) {
|
|
4888
4901
|
const oldest = state.skippedKeys.keys().next().value;
|
|
4889
4902
|
if (oldest !== void 0) state.skippedKeys.delete(oldest);
|
|
4903
|
+
else break;
|
|
4890
4904
|
}
|
|
4891
4905
|
}
|
|
4892
4906
|
const { nextChainKey, messageKey } = await ratchetStep(ck);
|
|
@@ -4973,8 +4987,11 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
4973
4987
|
}
|
|
4974
4988
|
this._seenMessageIds.add(msg.id);
|
|
4975
4989
|
if (this._seenMessageIds.size > 1e4) {
|
|
4976
|
-
const
|
|
4977
|
-
|
|
4990
|
+
const iter = this._seenMessageIds.values();
|
|
4991
|
+
for (let i = 0; i < 1e3; i++) {
|
|
4992
|
+
const v = iter.next().value;
|
|
4993
|
+
if (v !== void 0) this._seenMessageIds.delete(v);
|
|
4994
|
+
}
|
|
4978
4995
|
}
|
|
4979
4996
|
decrypted.push({
|
|
4980
4997
|
id: msg.id,
|
|
@@ -5055,8 +5072,12 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
5055
5072
|
const profile = await res.json();
|
|
5056
5073
|
this._identityCache.set(did, { profile, cachedAt: Date.now() });
|
|
5057
5074
|
if (this._identityCache.size > 500) {
|
|
5058
|
-
const
|
|
5059
|
-
|
|
5075
|
+
const excess = this._identityCache.size - 400;
|
|
5076
|
+
const iter = this._identityCache.keys();
|
|
5077
|
+
for (let i = 0; i < excess; i++) {
|
|
5078
|
+
const key = iter.next().value;
|
|
5079
|
+
if (key !== void 0) this._identityCache.delete(key);
|
|
5080
|
+
}
|
|
5060
5081
|
}
|
|
5061
5082
|
return profile;
|
|
5062
5083
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -4352,14 +4352,16 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
4352
4352
|
method: "PUT",
|
|
4353
4353
|
headers: { "Content-Type": "application/json", "X-Agent-Key": this.apiKey },
|
|
4354
4354
|
body: JSON.stringify({ value: blob })
|
|
4355
|
-
}).catch(() => {
|
|
4355
|
+
}).catch((e) => {
|
|
4356
|
+
console.warn(`[voidly] \u26A0 Relay ratchet persist failed: ${e instanceof Error ? e.message : e}`);
|
|
4356
4357
|
});
|
|
4357
4358
|
break;
|
|
4358
4359
|
case "custom":
|
|
4359
4360
|
if (this._onPersist) await this._onPersist(blob);
|
|
4360
4361
|
break;
|
|
4361
4362
|
}
|
|
4362
|
-
} catch {
|
|
4363
|
+
} catch (e) {
|
|
4364
|
+
console.warn(`[voidly] \u26A0 Ratchet state persistence failed (${this._persistMode}): ${e instanceof Error ? e.message : e}`);
|
|
4363
4365
|
}
|
|
4364
4366
|
}
|
|
4365
4367
|
/** Load persisted ratchet state and restore into memory */
|
|
@@ -4394,14 +4396,16 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
4394
4396
|
const data = await res.json();
|
|
4395
4397
|
blob = data.value;
|
|
4396
4398
|
}
|
|
4397
|
-
} catch {
|
|
4399
|
+
} catch (e) {
|
|
4400
|
+
console.warn(`[voidly] \u26A0 Relay ratchet load failed: ${e instanceof Error ? e.message : e}`);
|
|
4398
4401
|
}
|
|
4399
4402
|
break;
|
|
4400
4403
|
case "custom":
|
|
4401
4404
|
if (this._onLoad) blob = await this._onLoad();
|
|
4402
4405
|
break;
|
|
4403
4406
|
}
|
|
4404
|
-
} catch {
|
|
4407
|
+
} catch (e) {
|
|
4408
|
+
console.warn(`[voidly] \u26A0 Ratchet state load failed (${this._persistMode}): ${e instanceof Error ? e.message : e}`);
|
|
4405
4409
|
return;
|
|
4406
4410
|
}
|
|
4407
4411
|
if (!blob) return;
|
|
@@ -4413,6 +4417,12 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
4413
4417
|
const states = JSON.parse((0, import_tweetnacl_util.encodeUTF8)(decrypted));
|
|
4414
4418
|
for (const [pairId, rs] of Object.entries(states)) {
|
|
4415
4419
|
if (this._ratchetStates.has(pairId)) continue;
|
|
4420
|
+
if (!rs || typeof rs.sendStep !== "number" || typeof rs.recvStep !== "number" || typeof rs.sendChainKey !== "string" || typeof rs.recvChainKey !== "string") {
|
|
4421
|
+
continue;
|
|
4422
|
+
}
|
|
4423
|
+
if (rs.sendStep < 0 || rs.recvStep < 0 || rs.sendStep > 4294967295 || rs.recvStep > 4294967295) {
|
|
4424
|
+
continue;
|
|
4425
|
+
}
|
|
4416
4426
|
const state = {
|
|
4417
4427
|
sendChainKey: (0, import_tweetnacl_util.decodeBase64)(rs.sendChainKey),
|
|
4418
4428
|
sendStep: rs.sendStep,
|
|
@@ -4420,20 +4430,21 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
4420
4430
|
recvStep: rs.recvStep,
|
|
4421
4431
|
skippedKeys: /* @__PURE__ */ new Map()
|
|
4422
4432
|
};
|
|
4423
|
-
if (rs.rootKey) state.rootKey = (0, import_tweetnacl_util.decodeBase64)(rs.rootKey);
|
|
4424
|
-
if (rs.dhSendSecretKey && rs.dhSendPublicKey) {
|
|
4433
|
+
if (rs.rootKey && typeof rs.rootKey === "string") state.rootKey = (0, import_tweetnacl_util.decodeBase64)(rs.rootKey);
|
|
4434
|
+
if (typeof rs.dhSendSecretKey === "string" && typeof rs.dhSendPublicKey === "string") {
|
|
4425
4435
|
state.dhSendKeyPair = {
|
|
4426
4436
|
secretKey: (0, import_tweetnacl_util.decodeBase64)(rs.dhSendSecretKey),
|
|
4427
4437
|
publicKey: (0, import_tweetnacl_util.decodeBase64)(rs.dhSendPublicKey)
|
|
4428
4438
|
};
|
|
4429
4439
|
}
|
|
4430
|
-
if (rs.dhRecvPubKey) state.dhRecvPubKey = (0, import_tweetnacl_util.decodeBase64)(rs.dhRecvPubKey);
|
|
4431
|
-
if (rs.prevSendStep
|
|
4440
|
+
if (typeof rs.dhRecvPubKey === "string") state.dhRecvPubKey = (0, import_tweetnacl_util.decodeBase64)(rs.dhRecvPubKey);
|
|
4441
|
+
if (typeof rs.prevSendStep === "number" && rs.prevSendStep >= 0) state.prevSendStep = rs.prevSendStep;
|
|
4432
4442
|
if (state.sendChainKey.length === 32 && state.recvChainKey.length === 32) {
|
|
4433
4443
|
this._ratchetStates.set(pairId, state);
|
|
4434
4444
|
}
|
|
4435
4445
|
}
|
|
4436
|
-
} catch {
|
|
4446
|
+
} catch (e) {
|
|
4447
|
+
console.warn(`[voidly] \u26A0 Ratchet state restore failed (corrupt data, starting fresh): ${e instanceof Error ? e.message : e}`);
|
|
4437
4448
|
}
|
|
4438
4449
|
}
|
|
4439
4450
|
/** IndexedDB put helper (browser only) */
|
|
@@ -4758,16 +4769,16 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
4758
4769
|
if (msg.envelope) {
|
|
4759
4770
|
try {
|
|
4760
4771
|
const env = JSON.parse(msg.envelope);
|
|
4761
|
-
if (typeof env.ratchet_step === "number") {
|
|
4772
|
+
if (typeof env.ratchet_step === "number" && Number.isInteger(env.ratchet_step) && env.ratchet_step >= 0 && env.ratchet_step <= 4294967295) {
|
|
4762
4773
|
envelopeRatchetStep = env.ratchet_step;
|
|
4763
4774
|
}
|
|
4764
|
-
if (typeof env.pq_ciphertext === "string") {
|
|
4775
|
+
if (typeof env.pq_ciphertext === "string" && env.pq_ciphertext.length <= 65536) {
|
|
4765
4776
|
envelopePqCiphertext = env.pq_ciphertext;
|
|
4766
4777
|
}
|
|
4767
|
-
if (typeof env.dh_ratchet_key === "string") {
|
|
4778
|
+
if (typeof env.dh_ratchet_key === "string" && env.dh_ratchet_key.length <= 256) {
|
|
4768
4779
|
envelopeDhRatchetKey = env.dh_ratchet_key;
|
|
4769
4780
|
}
|
|
4770
|
-
if (typeof env.pn === "number") {
|
|
4781
|
+
if (typeof env.pn === "number" && Number.isInteger(env.pn) && env.pn >= 0 && env.pn <= 4294967295) {
|
|
4771
4782
|
envelopePn = env.pn;
|
|
4772
4783
|
}
|
|
4773
4784
|
} catch {
|
|
@@ -4832,9 +4843,10 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
4832
4843
|
if (!state.dhSkippedKeys) state.dhSkippedKeys = /* @__PURE__ */ new Map();
|
|
4833
4844
|
state.dhSkippedKeys.set(skipKey, skippedMk);
|
|
4834
4845
|
ck = nextChainKey;
|
|
4835
|
-
|
|
4846
|
+
while (state.dhSkippedKeys.size > MAX_SKIP) {
|
|
4836
4847
|
const oldest = state.dhSkippedKeys.keys().next().value;
|
|
4837
4848
|
if (oldest !== void 0) state.dhSkippedKeys.delete(oldest);
|
|
4849
|
+
else break;
|
|
4838
4850
|
}
|
|
4839
4851
|
}
|
|
4840
4852
|
}
|
|
@@ -4867,16 +4879,18 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
4867
4879
|
} else if (targetStep > state.recvStep) {
|
|
4868
4880
|
const skip = targetStep - state.recvStep;
|
|
4869
4881
|
if (skip > MAX_SKIP) {
|
|
4870
|
-
|
|
4882
|
+
this._decryptFailCount++;
|
|
4883
|
+
continue;
|
|
4871
4884
|
} else {
|
|
4872
4885
|
let ck = state.recvChainKey;
|
|
4873
4886
|
for (let i = state.recvStep + 1; i < targetStep; i++) {
|
|
4874
4887
|
const { nextChainKey: nextChainKey2, messageKey: skippedMk } = await ratchetStep(ck);
|
|
4875
4888
|
state.skippedKeys.set(i, skippedMk);
|
|
4876
4889
|
ck = nextChainKey2;
|
|
4877
|
-
|
|
4890
|
+
while (state.skippedKeys.size > MAX_SKIP) {
|
|
4878
4891
|
const oldest = state.skippedKeys.keys().next().value;
|
|
4879
4892
|
if (oldest !== void 0) state.skippedKeys.delete(oldest);
|
|
4893
|
+
else break;
|
|
4880
4894
|
}
|
|
4881
4895
|
}
|
|
4882
4896
|
const { nextChainKey, messageKey } = await ratchetStep(ck);
|
|
@@ -4963,8 +4977,11 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
4963
4977
|
}
|
|
4964
4978
|
this._seenMessageIds.add(msg.id);
|
|
4965
4979
|
if (this._seenMessageIds.size > 1e4) {
|
|
4966
|
-
const
|
|
4967
|
-
|
|
4980
|
+
const iter = this._seenMessageIds.values();
|
|
4981
|
+
for (let i = 0; i < 1e3; i++) {
|
|
4982
|
+
const v = iter.next().value;
|
|
4983
|
+
if (v !== void 0) this._seenMessageIds.delete(v);
|
|
4984
|
+
}
|
|
4968
4985
|
}
|
|
4969
4986
|
decrypted.push({
|
|
4970
4987
|
id: msg.id,
|
|
@@ -5045,8 +5062,12 @@ var VoidlyAgent = class _VoidlyAgent {
|
|
|
5045
5062
|
const profile = await res.json();
|
|
5046
5063
|
this._identityCache.set(did, { profile, cachedAt: Date.now() });
|
|
5047
5064
|
if (this._identityCache.size > 500) {
|
|
5048
|
-
const
|
|
5049
|
-
|
|
5065
|
+
const excess = this._identityCache.size - 400;
|
|
5066
|
+
const iter = this._identityCache.keys();
|
|
5067
|
+
for (let i = 0; i < excess; i++) {
|
|
5068
|
+
const key = iter.next().value;
|
|
5069
|
+
if (key !== void 0) this._identityCache.delete(key);
|
|
5070
|
+
}
|
|
5050
5071
|
}
|
|
5051
5072
|
return profile;
|
|
5052
5073
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Post-Quantum + Ratchet Persistence — Future-proof encrypted messaging.
|
|
4
|
+
*
|
|
5
|
+
* Run: node examples/post-quantum.mjs
|
|
6
|
+
*
|
|
7
|
+
* Features demonstrated:
|
|
8
|
+
* 1. ML-KEM-768 hybrid key exchange (NIST FIPS 203) — quantum-resistant
|
|
9
|
+
* 2. Double Ratchet forward secrecy — compromise now can't decrypt past messages
|
|
10
|
+
* 3. Ratchet persistence — session survives agent restarts
|
|
11
|
+
* 4. Sealed sender — relay can't see who sent the message
|
|
12
|
+
* 5. Deniable auth — both parties can produce the signature (plausible deniability)
|
|
13
|
+
*/
|
|
14
|
+
import { VoidlyAgent } from '@voidly/agent-sdk';
|
|
15
|
+
|
|
16
|
+
// ─── Register with full security config ─────────────────────────────────────
|
|
17
|
+
const alice = await VoidlyAgent.register(
|
|
18
|
+
{ name: 'pq-alice', capabilities: ['chat', 'intel'] },
|
|
19
|
+
{
|
|
20
|
+
pq: true, // Enable ML-KEM-768 post-quantum hybrid
|
|
21
|
+
padding: true, // Pad all messages to power-of-2 (traffic analysis resistance)
|
|
22
|
+
sealedSender: true, // Hide sender DID from relay metadata
|
|
23
|
+
deniable: true, // HMAC signatures (both parties can produce — deniable)
|
|
24
|
+
persist: 'memory', // Ratchet state persists in memory (use 'file' or 'relay' for disk)
|
|
25
|
+
autoPin: true, // TOFU: pin first-seen keys, warn on change
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const bob = await VoidlyAgent.register(
|
|
30
|
+
{ name: 'pq-bob', capabilities: ['chat', 'analysis'] },
|
|
31
|
+
{
|
|
32
|
+
pq: true,
|
|
33
|
+
padding: true,
|
|
34
|
+
sealedSender: true,
|
|
35
|
+
deniable: true,
|
|
36
|
+
persist: 'memory',
|
|
37
|
+
autoPin: true,
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
console.log(`Alice: ${alice.did} (PQ + sealed + deniable)`);
|
|
42
|
+
console.log(`Bob: ${bob.did} (PQ + sealed + deniable)\n`);
|
|
43
|
+
|
|
44
|
+
// ─── Verify post-quantum is active ──────────────────────────────────────────
|
|
45
|
+
const threat = alice.threatModel();
|
|
46
|
+
console.log('Active protections:');
|
|
47
|
+
threat.protections.forEach(p => console.log(` ✓ ${p}`));
|
|
48
|
+
console.log('Known gaps:');
|
|
49
|
+
threat.gaps.forEach(g => console.log(` ⚠ ${g}`));
|
|
50
|
+
|
|
51
|
+
// ─── Send messages with full protection stack ───────────────────────────────
|
|
52
|
+
console.log('\nSending with PQ hybrid + Double Ratchet + sealed sender + padding...');
|
|
53
|
+
await alice.send(bob.did, 'Quantum-resistant hello from Alice', { threadId: 'pq-demo' });
|
|
54
|
+
await alice.send(bob.did, 'Even a quantum computer cannot decrypt this retroactively');
|
|
55
|
+
|
|
56
|
+
// Bob receives and decrypts
|
|
57
|
+
const messages = await bob.receive({ limit: 10 });
|
|
58
|
+
for (const msg of messages) {
|
|
59
|
+
console.log(`\n Bob received: "${msg.content}"`);
|
|
60
|
+
console.log(` From: ${msg.from.slice(0, 30)}...`);
|
|
61
|
+
console.log(` Signature valid: ${msg.signatureValid}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── Demonstrate forward secrecy ────────────────────────────────────────────
|
|
65
|
+
console.log('\n─── Forward Secrecy Demo ───');
|
|
66
|
+
console.log('Ratchet advances with each message — past keys are deleted.');
|
|
67
|
+
console.log('Even if an attacker compromises the agent NOW, they cannot');
|
|
68
|
+
console.log('decrypt messages that were already received and processed.\n');
|
|
69
|
+
|
|
70
|
+
// Bob replies — DH ratchet advances
|
|
71
|
+
await bob.send(alice.did, 'Reply from Bob — ratchet advanced');
|
|
72
|
+
const replies = await alice.receive({ limit: 5 });
|
|
73
|
+
for (const r of replies) {
|
|
74
|
+
console.log(` Alice received: "${r.content}"`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ─── Credential export (includes ratchet state) ─────────────────────────────
|
|
78
|
+
console.log('\n─── Credential Export ───');
|
|
79
|
+
const creds = alice.exportCredentials();
|
|
80
|
+
console.log(` DID: ${creds.did}`);
|
|
81
|
+
console.log(` Signing key: ${creds.signingSecretKey.slice(0, 16)}...`);
|
|
82
|
+
console.log(` PQ enabled: ${!!creds.mlkemSecretKey}`);
|
|
83
|
+
|
|
84
|
+
// Restore agent from credentials (e.g., after restart)
|
|
85
|
+
const restored = VoidlyAgent.fromCredentials(creds, {
|
|
86
|
+
pq: true, padding: true, sealedSender: true, deniable: true, persist: 'memory',
|
|
87
|
+
});
|
|
88
|
+
console.log(` Restored DID: ${restored.did} (matches: ${restored.did === alice.did})`);
|
|
89
|
+
|
|
90
|
+
// ─── Flush ratchet state ────────────────────────────────────────────────────
|
|
91
|
+
await alice.flushRatchetState();
|
|
92
|
+
console.log('\n Ratchet state flushed to persistence backend.');
|
|
93
|
+
|
|
94
|
+
// Clean shutdown
|
|
95
|
+
alice.stopAll();
|
|
96
|
+
bob.stopAll();
|
|
97
|
+
restored.stopAll();
|
|
98
|
+
|
|
99
|
+
console.log('\n✓ Done — post-quantum hybrid encryption with forward secrecy.');
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SSE Streaming — Real-time message delivery via Server-Sent Events.
|
|
4
|
+
*
|
|
5
|
+
* Run: node examples/sse-streaming.mjs
|
|
6
|
+
*
|
|
7
|
+
* Instead of polling, Bob opens an SSE connection to the relay.
|
|
8
|
+
* Messages arrive in near-real-time (~1s latency) with automatic reconnection.
|
|
9
|
+
* All decryption still happens client-side.
|
|
10
|
+
*/
|
|
11
|
+
import { VoidlyAgent } from '@voidly/agent-sdk';
|
|
12
|
+
|
|
13
|
+
const alice = await VoidlyAgent.register({ name: 'sse-alice' });
|
|
14
|
+
const bob = await VoidlyAgent.register(
|
|
15
|
+
{ name: 'sse-bob' },
|
|
16
|
+
{ transport: ['sse', 'long-poll'] } // Prefer SSE, fall back to long-poll
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
console.log(`Alice: ${alice.did}`);
|
|
20
|
+
console.log(`Bob: ${bob.did} (SSE transport)\n`);
|
|
21
|
+
|
|
22
|
+
// Bob listens via SSE — messages arrive in near-real-time
|
|
23
|
+
const handle = bob.listen(
|
|
24
|
+
(msg) => {
|
|
25
|
+
console.log(` ← Bob received: "${msg.content}" from ${msg.from.slice(0, 24)}...`);
|
|
26
|
+
console.log(` Signature valid: ${msg.signatureValid}`);
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
interval: 2000, // Reconnect interval if SSE drops
|
|
30
|
+
adaptive: true, // Back off when idle
|
|
31
|
+
heartbeat: false, // Don't send pings
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// Wait for SSE connection to establish
|
|
36
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
37
|
+
|
|
38
|
+
// Alice sends a burst of messages
|
|
39
|
+
console.log('Alice sending 3 messages...');
|
|
40
|
+
await alice.send(bob.did, 'Message 1 — SSE delivery');
|
|
41
|
+
await alice.send(bob.did, 'Message 2 — near-real-time');
|
|
42
|
+
await alice.send(bob.did, 'Message 3 — all encrypted');
|
|
43
|
+
|
|
44
|
+
// Wait for delivery
|
|
45
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
46
|
+
|
|
47
|
+
// Clean shutdown
|
|
48
|
+
handle.stop();
|
|
49
|
+
alice.stopAll();
|
|
50
|
+
bob.stopAll();
|
|
51
|
+
|
|
52
|
+
console.log('\n✓ Done — SSE streaming with E2E encryption.');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voidly/agent-sdk",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.7",
|
|
4
4
|
"description": "E2E encrypted agent-to-agent communication SDK — Double Ratchet, X3DH, deniable auth, ML-KEM-768 post-quantum, SSE streaming, ratchet persistence, multi-relay federation",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|