dignity.js 0.2.0 → 0.3.0

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/README.md CHANGED
@@ -13,10 +13,9 @@
13
13
  <p align="center">
14
14
  <a href="https://www.npmjs.com/package/dignity.js"><img src="https://img.shields.io/npm/v/dignity.js?color=cb3837&label=npm" alt="npm version"></a>
15
15
  <a href="https://www.npmjs.com/package/dignity.js"><img src="https://img.shields.io/npm/dm/dignity.js?color=blue" alt="npm downloads"></a>
16
- <img src="https://img.shields.io/badge/tests-29%20passing-brightgreen" alt="tests passing">
17
- <img src="https://img.shields.io/badge/coverage-88%25-brightgreen" alt="coverage">
18
- <img src="https://img.shields.io/badge/license-MIT-black" alt="license">
19
- <img src="https://img.shields.io/badge/minified-51KB-purple" alt="bundle size">
16
+ <img src="https://img.shields.io/badge/tests-122%20passing-brightgreen" alt="tests passing">
17
+ <img src="https://img.shields.io/badge/coverage-97%25-brightgreen" alt="coverage">
18
+ <img src="https://img.shields.io/badge/license-Apache%202.0-black" alt="license">
20
19
  </p>
21
20
 
22
21
  REST-like P2P object API for decentralized JavaScript applications.
@@ -35,7 +34,7 @@ REST-like P2P object API for decentralized JavaScript applications.
35
34
  - default `powSteps: 22` (calibrated on this machine to about 1000ms)
36
35
  - automatic peer ban on invalid signature/PoW (`48h` default)
37
36
  - Team/subapp scoped broadcast passwords (`broadcastScope` + `broadcastPasswords`)
38
- - Browser-first distribution with minified build (`dist/dignity.min.js`)
37
+ - Browser-first: published npm package includes IIFE, ESM, and CJS builds
39
38
 
40
39
  ## Install
41
40
 
@@ -146,15 +145,9 @@ bob.registerPeerPublicKey('alice', alice.getPublicKey());
146
145
  await alice.sendDirectMessage('bob', 'dm', { text: 'private payload' });
147
146
  ```
148
147
 
149
- ## Browser Builds
148
+ ## Browser Usage
150
149
 
151
- Generated artifacts:
152
-
153
- - `dist/dignity.min.js` (IIFE, global `DignityJS`)
154
- - `dist/dignity.esm.js` (ESM)
155
- - `dist/dignity.cjs.js` (CommonJS)
156
-
157
- Example with CDN:
150
+ The published npm package includes pre-built bundles (IIFE, ESM, CJS) generated at publish time. The `dist/` folder is not checked into the repository.
158
151
 
159
152
  ```html
160
153
  <script src="https://unpkg.com/dignity.js/dist/dignity.min.js"></script>
@@ -163,6 +156,19 @@ Example with CDN:
163
156
  </script>
164
157
  ```
165
158
 
159
+ ## Security Model
160
+
161
+ `dignity.js` provides two encryption modes:
162
+
163
+ - **Direct mode** (`targetId` set): true end-to-end encryption using X25519 key exchange between sender and recipient. Only the intended recipient can decrypt.
164
+ - **Broadcast mode** (no `targetId`): symmetric encryption using a shared password. All peers that know the password can decrypt all broadcast traffic in that scope. This is a **group shared-secret cipher**, not end-to-end encryption.
165
+
166
+ Broadcast encryption uses PBKDF2-SHA256 (default 100,000 iterations) with a random salt per message to derive the symmetric key. This protects against offline brute-force of weak passwords. The iteration count is configurable via `kdfIterations`.
167
+
168
+ Messages from peers running older versions that used the legacy single-hash KDF are still accepted and decrypted automatically (backward compatible).
169
+
170
+ **Important:** if the broadcast password leaks, all past captured traffic for that scope is retroactively decryptable. For sensitive data, use direct mode with per-peer public keys.
171
+
166
172
  ## Signaling Servers
167
173
 
168
174
  Default signaling URLs include PeerJS-compatible public endpoints:
@@ -198,11 +204,11 @@ npm run test:pow-calibrate
198
204
  ## Publish
199
205
 
200
206
  ```bash
201
- npm test
202
- npm run build
203
207
  npm publish --access public
204
208
  ```
205
209
 
210
+ The `prepublishOnly` script runs tests and build automatically.
211
+
206
212
  ## License
207
213
 
208
- Apache 2.0
214
+ Apache 2.0 — see [LICENSE](LICENSE).
@@ -2434,7 +2434,8 @@ var require_message_security_service = __commonJS({
2434
2434
  broadcastPasswords: {},
2435
2435
  resolveBroadcastPassword: null,
2436
2436
  powSteps: 22,
2437
- trustedPeerKeys: {}
2437
+ trustedPeerKeys: {},
2438
+ kdfIterations: 1e5
2438
2439
  };
2439
2440
  function stableStringify(value) {
2440
2441
  if (value === null || typeof value !== "object") {
@@ -2461,6 +2462,33 @@ var require_message_security_service = __commonJS({
2461
2462
  function utf8ToBytes(value) {
2462
2463
  return naclUtil.decodeUTF8(value);
2463
2464
  }
2465
+ async function deriveBroadcastKey(password, salt, iterations) {
2466
+ const subtle = globalThis.crypto && globalThis.crypto.subtle;
2467
+ if (subtle) {
2468
+ const keyMaterial = await subtle.importKey(
2469
+ "raw",
2470
+ utf8ToBytes(password),
2471
+ "PBKDF2",
2472
+ false,
2473
+ ["deriveBits"]
2474
+ );
2475
+ const bits = await subtle.deriveBits(
2476
+ { name: "PBKDF2", salt, iterations, hash: "SHA-256" },
2477
+ keyMaterial,
2478
+ 256
2479
+ );
2480
+ return new Uint8Array(bits);
2481
+ }
2482
+ try {
2483
+ const { pbkdf2Sync } = require("crypto");
2484
+ return new Uint8Array(pbkdf2Sync(password, Buffer.from(salt), iterations, 32, "sha256"));
2485
+ } catch (_ignored) {
2486
+ return hash32(concatBytes(utf8ToBytes(password), salt));
2487
+ }
2488
+ }
2489
+ function legacyBroadcastKey(password, salt) {
2490
+ return hash32(concatBytes(utf8ToBytes(password), salt));
2491
+ }
2464
2492
  function normalizePeerPublicKey(publicKey) {
2465
2493
  if (!publicKey || typeof publicKey !== "object") {
2466
2494
  throw new Error("Public key must be an object with signingPublicKey and encryptionPublicKey");
@@ -2621,7 +2649,7 @@ var require_message_security_service = __commonJS({
2621
2649
  if (envelope.security && envelope.security.signing && envelope.security.signing.enabled && this.options.signingEnabled) {
2622
2650
  this.verifySignature(envelope);
2623
2651
  }
2624
- const payload = this.decryptPayload(envelope);
2652
+ const payload = await this.decryptPayload(envelope);
2625
2653
  return {
2626
2654
  ignored: false,
2627
2655
  messageType: envelope.messageType,
@@ -2686,7 +2714,8 @@ var require_message_security_service = __commonJS({
2686
2714
  const nonce = nacl.randomBytes(nacl.secretbox.nonceLength);
2687
2715
  const salt = nacl.randomBytes(16);
2688
2716
  const password = this.resolveBroadcastPassword(scope);
2689
- const key = hash32(concatBytes(utf8ToBytes(password), salt));
2717
+ const iterations = this.options.kdfIterations || DEFAULT_SECURITY_OPTIONS2.kdfIterations;
2718
+ const key = await deriveBroadcastKey(password, salt, iterations);
2690
2719
  const encrypted = nacl.secretbox(plainText, nonce, key);
2691
2720
  return {
2692
2721
  payload: naclUtil.encodeBase64(encrypted),
@@ -2695,11 +2724,13 @@ var require_message_security_service = __commonJS({
2695
2724
  mode: "broadcast",
2696
2725
  scope,
2697
2726
  nonce: naclUtil.encodeBase64(nonce),
2698
- salt: naclUtil.encodeBase64(salt)
2727
+ salt: naclUtil.encodeBase64(salt),
2728
+ kdf: "pbkdf2",
2729
+ kdfIterations: iterations
2699
2730
  }
2700
2731
  };
2701
2732
  }
2702
- decryptPayload(envelope) {
2733
+ async decryptPayload(envelope) {
2703
2734
  const encryption = envelope.security ? envelope.security.encryption : null;
2704
2735
  if (!encryption || !encryption.enabled || !this.options.encryptionEnabled) {
2705
2736
  return envelope.payload;
@@ -2710,7 +2741,13 @@ var require_message_security_service = __commonJS({
2710
2741
  const password = this.resolveBroadcastPassword(scope);
2711
2742
  const salt = naclUtil.decodeBase64(encryption.salt);
2712
2743
  const nonce = naclUtil.decodeBase64(encryption.nonce);
2713
- const key = hash32(concatBytes(utf8ToBytes(password), salt));
2744
+ let key;
2745
+ if (encryption.kdf === "pbkdf2") {
2746
+ const iterations = encryption.kdfIterations || DEFAULT_SECURITY_OPTIONS2.kdfIterations;
2747
+ key = await deriveBroadcastKey(password, salt, iterations);
2748
+ } else {
2749
+ key = legacyBroadcastKey(password, salt);
2750
+ }
2714
2751
  const decrypted = nacl.secretbox.open(encryptedBuffer, nonce, key);
2715
2752
  if (!decrypted) {
2716
2753
  throw new Error("Unable to decrypt broadcast payload");
@@ -2804,6 +2841,8 @@ var require_message_security_service = __commonJS({
2804
2841
  module2.exports = {
2805
2842
  MessageSecurityService: MessageSecurityService2,
2806
2843
  stableStringify,
2844
+ deriveBroadcastKey,
2845
+ legacyBroadcastKey,
2807
2846
  DEFAULT_SECURITY_OPTIONS: DEFAULT_SECURITY_OPTIONS2
2808
2847
  };
2809
2848
  }