@agora-sdk/secure-chat-core 0.3.0 → 0.5.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.
Files changed (67) hide show
  1. package/dist/cjs/backup/passphrase-strength.d.ts +22 -0
  2. package/dist/cjs/backup/passphrase-strength.js +75 -0
  3. package/dist/cjs/backup/passphrase-strength.js.map +1 -0
  4. package/dist/cjs/context/secure-chat-context.d.ts +12 -1
  5. package/dist/cjs/context/secure-chat-context.js +3 -1
  6. package/dist/cjs/context/secure-chat-context.js.map +1 -1
  7. package/dist/cjs/contract/index.d.ts +2 -150
  8. package/dist/cjs/contract/index.js +11 -10
  9. package/dist/cjs/contract/index.js.map +1 -1
  10. package/dist/cjs/hooks/useSecureBackup.d.ts +67 -0
  11. package/dist/cjs/hooks/useSecureBackup.js +207 -0
  12. package/dist/cjs/hooks/useSecureBackup.js.map +1 -0
  13. package/dist/cjs/hooks/useSecureDevice.d.ts +21 -1
  14. package/dist/cjs/hooks/useSecureDevice.js +46 -5
  15. package/dist/cjs/hooks/useSecureDevice.js.map +1 -1
  16. package/dist/cjs/hooks/useSecureMessages.d.ts +16 -3
  17. package/dist/cjs/hooks/useSecureMessages.js +38 -15
  18. package/dist/cjs/hooks/useSecureMessages.js.map +1 -1
  19. package/dist/cjs/hooks/useSecureSafetyNumber.d.ts +30 -0
  20. package/dist/cjs/hooks/useSecureSafetyNumber.js +82 -0
  21. package/dist/cjs/hooks/useSecureSafetyNumber.js.map +1 -0
  22. package/dist/cjs/index.d.ts +12 -1
  23. package/dist/cjs/index.js +16 -1
  24. package/dist/cjs/index.js.map +1 -1
  25. package/dist/cjs/transport/socket.d.ts +15 -3
  26. package/dist/cjs/transport/socket.js +4 -2
  27. package/dist/cjs/transport/socket.js.map +1 -1
  28. package/dist/cjs/util/padding.d.ts +39 -0
  29. package/dist/cjs/util/padding.js +80 -0
  30. package/dist/cjs/util/padding.js.map +1 -0
  31. package/dist/cjs/util/safety-number.d.ts +27 -0
  32. package/dist/cjs/util/safety-number.js +84 -0
  33. package/dist/cjs/util/safety-number.js.map +1 -0
  34. package/dist/esm/backup/passphrase-strength.d.ts +22 -0
  35. package/dist/esm/backup/passphrase-strength.js +72 -0
  36. package/dist/esm/backup/passphrase-strength.js.map +1 -0
  37. package/dist/esm/context/secure-chat-context.d.ts +12 -1
  38. package/dist/esm/context/secure-chat-context.js +3 -1
  39. package/dist/esm/context/secure-chat-context.js.map +1 -1
  40. package/dist/esm/contract/index.d.ts +2 -150
  41. package/dist/esm/contract/index.js +11 -10
  42. package/dist/esm/contract/index.js.map +1 -1
  43. package/dist/esm/hooks/useSecureBackup.d.ts +67 -0
  44. package/dist/esm/hooks/useSecureBackup.js +204 -0
  45. package/dist/esm/hooks/useSecureBackup.js.map +1 -0
  46. package/dist/esm/hooks/useSecureDevice.d.ts +21 -1
  47. package/dist/esm/hooks/useSecureDevice.js +46 -5
  48. package/dist/esm/hooks/useSecureDevice.js.map +1 -1
  49. package/dist/esm/hooks/useSecureMessages.d.ts +16 -3
  50. package/dist/esm/hooks/useSecureMessages.js +38 -15
  51. package/dist/esm/hooks/useSecureMessages.js.map +1 -1
  52. package/dist/esm/hooks/useSecureSafetyNumber.d.ts +30 -0
  53. package/dist/esm/hooks/useSecureSafetyNumber.js +79 -0
  54. package/dist/esm/hooks/useSecureSafetyNumber.js.map +1 -0
  55. package/dist/esm/index.d.ts +12 -1
  56. package/dist/esm/index.js +7 -0
  57. package/dist/esm/index.js.map +1 -1
  58. package/dist/esm/transport/socket.d.ts +15 -3
  59. package/dist/esm/transport/socket.js +4 -2
  60. package/dist/esm/transport/socket.js.map +1 -1
  61. package/dist/esm/util/padding.d.ts +39 -0
  62. package/dist/esm/util/padding.js +75 -0
  63. package/dist/esm/util/padding.js.map +1 -0
  64. package/dist/esm/util/safety-number.d.ts +27 -0
  65. package/dist/esm/util/safety-number.js +81 -0
  66. package/dist/esm/util/safety-number.js.map +1 -0
  67. package/package.json +3 -2
@@ -0,0 +1,79 @@
1
+ // useSecureSafetyNumber — derive the key-verification safety number for a DM conversation.
2
+ //
3
+ // Where it sits in the blind-server model: the server is untrusted and could swap a peer's KeyPackage
4
+ // (active MITM on a TOFU system). This hook reads the two devices' PUBLIC identity keys from the local
5
+ // MLS roster (crypto.exportGroupIdentities) and computes a symmetric, human-comparable safety number
6
+ // (see util/safety-number) the two users confirm out-of-band. Headless: the styled UI lives in the app
7
+ // / demo. DM-focused — for anything other than a 2-member group it returns null rather than guess.
8
+ import { useCallback, useEffect, useRef, useState } from "react";
9
+ import { computeSafetyNumber } from "../util/safety-number.js";
10
+ import { useSecureChat } from "../context/secure-chat-context.js";
11
+ /**
12
+ * Compute the out-of-band key-verification safety number for a direct-message conversation.
13
+ *
14
+ * Resolves the conversation's MLS group, reads its two members' public signature keys, and derives a
15
+ * symmetric {@link SafetyNumber}. Recomputes when the conversation's group handle advances (e.g. a
16
+ * processed Commit changes the roster). Returns `safetyNumber: null` when the group is unresolved or is
17
+ * not a 2-member DM.
18
+ *
19
+ * @param conversationId - The conversation to compute the safety number for.
20
+ * @returns {@link UseSecureSafetyNumberValues}.
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * const { safetyNumber } = useSecureSafetyNumber(conversationId);
25
+ * // render safetyNumber?.groups for the user to compare with their peer
26
+ * ```
27
+ */
28
+ export function useSecureSafetyNumber(conversationId) {
29
+ const { crypto, repo, resolveGroup, getGroupVersion, subscribeGroupChange } = useSecureChat();
30
+ const [safetyNumber, setSafetyNumber] = useState(null);
31
+ const [loading, setLoading] = useState(true);
32
+ const [error, setError] = useState(null);
33
+ const [tick, setTick] = useState(0);
34
+ // Recompute when this conversation's group handle advances (a roster change), without diffing handles.
35
+ const [groupVersion, setGroupVersion] = useState(0);
36
+ useEffect(() => {
37
+ return subscribeGroupChange(() => setGroupVersion(getGroupVersion(conversationId)));
38
+ }, [subscribeGroupChange, getGroupVersion, conversationId]);
39
+ const refresh = useCallback(() => setTick((t) => t + 1), []);
40
+ // Guards the per-run state writes against an out-of-date async resolve (conversation switch / unmount).
41
+ const runIdRef = useRef(0);
42
+ useEffect(() => {
43
+ const runId = ++runIdRef.current;
44
+ setLoading(true);
45
+ setError(null);
46
+ (async () => {
47
+ const group = await resolveGroup(conversationId);
48
+ if (!group)
49
+ return null;
50
+ const [persisted, members] = await Promise.all([
51
+ repo.loadDevice(),
52
+ crypto.exportGroupIdentities(group),
53
+ ]);
54
+ // DM only: need exactly the local device + one peer. Anything else → no safety number.
55
+ if (members.length !== 2 || !persisted)
56
+ return null;
57
+ const local = members.find((m) => m.deviceId === persisted.deviceId);
58
+ const remote = members.find((m) => m.deviceId !== persisted.deviceId);
59
+ if (!local || !remote)
60
+ return null;
61
+ return computeSafetyNumber(local, remote);
62
+ })()
63
+ .then((sn) => {
64
+ if (runIdRef.current === runId) {
65
+ setSafetyNumber(sn);
66
+ setLoading(false);
67
+ }
68
+ })
69
+ .catch((err) => {
70
+ if (runIdRef.current === runId) {
71
+ setError(err);
72
+ setSafetyNumber(null);
73
+ setLoading(false);
74
+ }
75
+ });
76
+ }, [crypto, repo, resolveGroup, conversationId, groupVersion, tick]);
77
+ return { safetyNumber, loading, error, refresh };
78
+ }
79
+ //# sourceMappingURL=useSecureSafetyNumber.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSecureSafetyNumber.js","sourceRoot":"","sources":["../../../src/hooks/useSecureSafetyNumber.tsx"],"names":[],"mappings":"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"}
@@ -8,14 +8,25 @@ export { useSecureMessages } from "./hooks/useSecureMessages.js";
8
8
  export type { UseSecureMessagesOptions, UseSecureMessagesValues, DecryptedSecureMessage, } from "./hooks/useSecureMessages.js";
9
9
  export { useSecureHandshakes } from "./hooks/useSecureHandshakes.js";
10
10
  export type { UseSecureHandshakesOptions, UseSecureHandshakesValues, } from "./hooks/useSecureHandshakes.js";
11
+ export { useSecureBackup } from "./hooks/useSecureBackup.js";
12
+ export type { UseSecureBackupValues } from "./hooks/useSecureBackup.js";
13
+ export { useSecureSafetyNumber } from "./hooks/useSecureSafetyNumber.js";
14
+ export type { UseSecureSafetyNumberValues } from "./hooks/useSecureSafetyNumber.js";
15
+ export { estimatePassphraseStrength } from "./backup/passphrase-strength.js";
16
+ export type { PassphraseStrength } from "./backup/passphrase-strength.js";
11
17
  export { SecureChatRestClient } from "./transport/rest.js";
12
18
  export type { SecureChatRestConfig } from "./transport/rest.js";
13
19
  export { SecureChatSocketClient } from "./transport/socket.js";
14
20
  export type { SecureSocket, SecureServerEvents, SecureClientEvents, SecureChatSocketConfig, } from "./transport/socket.js";
15
- export type { SecureChatCrypto, DeviceIdentity, KeyPackageBundle, GroupHandle, TargetedWelcome, CommitResult, PassphraseBackup, } from "@agora-sdk/secure-chat-crypto";
21
+ export type { SecureChatCrypto, DeviceIdentity, KeyPackageBundle, GroupHandle, GroupMemberIdentity, TargetedWelcome, CommitResult, PassphraseBackup, SecureDecryptFailureReason, } from "@agora-sdk/secure-chat-crypto";
22
+ export { SecureChatDecryptError } from "@agora-sdk/secure-chat-crypto";
16
23
  export type * from "./contract/index.js";
17
24
  export type { SecureChatStore } from "./persistence/store.js";
18
25
  export { MemoryStore } from "./persistence/memory-store.js";
19
26
  export { SecureChatRepository } from "./persistence/repository.js";
20
27
  export type { PersistedDevice } from "./persistence/repository.js";
21
28
  export { toBase64, fromBase64, utf8ToBytes, bytesToUtf8 } from "./util/base64.js";
29
+ export { padPlaintext, unpadPlaintext, nextBucket } from "./util/padding.js";
30
+ export type { PaddingPolicy } from "./util/padding.js";
31
+ export { computeSafetyNumber } from "./util/safety-number.js";
32
+ export type { SafetyNumber } from "./util/safety-number.js";
package/dist/esm/index.js CHANGED
@@ -10,11 +10,18 @@ export { useSecureDevice } from "./hooks/useSecureDevice.js";
10
10
  export { useSecureConversations } from "./hooks/useSecureConversations.js";
11
11
  export { useSecureMessages } from "./hooks/useSecureMessages.js";
12
12
  export { useSecureHandshakes } from "./hooks/useSecureHandshakes.js";
13
+ export { useSecureBackup } from "./hooks/useSecureBackup.js";
14
+ export { useSecureSafetyNumber } from "./hooks/useSecureSafetyNumber.js";
15
+ // ── backup (passphrase strength meter) ────────────────────────────────────────
16
+ export { estimatePassphraseStrength } from "./backup/passphrase-strength.js";
13
17
  // ── transport (for advanced / non-React use) ─────────────────────────────────
14
18
  export { SecureChatRestClient } from "./transport/rest.js";
15
19
  export { SecureChatSocketClient } from "./transport/socket.js";
20
+ export { SecureChatDecryptError } from "@agora-sdk/secure-chat-crypto";
16
21
  export { MemoryStore } from "./persistence/memory-store.js";
17
22
  export { SecureChatRepository } from "./persistence/repository.js";
18
23
  // ── utils ────────────────────────────────────────────────────────────────────
19
24
  export { toBase64, fromBase64, utf8ToBytes, bytesToUtf8 } from "./util/base64.js";
25
+ export { padPlaintext, unpadPlaintext, nextBucket } from "./util/padding.js";
26
+ export { computeSafetyNumber } from "./util/safety-number.js";
20
27
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,EAAE;AACF,6FAA6F;AAC7F,+FAA+F;AAC/F,4DAA4D;AAE5D,+EAA+E;AAC/E,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAMrF,gFAAgF;AAChF,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAE3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAMjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAMrE,gFAAgF;AAChF,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAwB/D,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAGnE,gFAAgF;AAChF,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,EAAE;AACF,6FAA6F;AAC7F,+FAA+F;AAC/F,4DAA4D;AAE5D,+EAA+E;AAC/E,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAMrF,gFAAgF;AAChF,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAE3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAMjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAKrE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AAGzE,iFAAiF;AACjF,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAG7E,gFAAgF;AAChF,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAoB/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAOvE,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAGnE,gFAAgF;AAChF,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE7E,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC"}
@@ -26,10 +26,22 @@ export interface SecureServerEvents {
26
26
  userId: string;
27
27
  }) => void;
28
28
  }
29
- /** Client → server events. */
29
+ /**
30
+ * Client → server events.
31
+ *
32
+ * @remarks
33
+ * Payloads are **objects**, not bare ids — the server destructures `{ conversationId }` /
34
+ * `{ deviceId }` off the first argument (agora-server `realtime/secure-socket.ts`). Emitting a bare
35
+ * string lands as `undefined` after destructuring (and, on a `null` payload, throws server-side), so
36
+ * the join silently fails. Keep these shapes in lockstep with the server's `SecureClientToServerEvents`.
37
+ */
30
38
  export interface SecureClientEvents {
31
- "join:secure-conversation": (conversationId: string) => void;
32
- "join:secure-device": (deviceId: string) => void;
39
+ "join:secure-conversation": (payload: {
40
+ conversationId: string;
41
+ }) => void;
42
+ "join:secure-device": (payload: {
43
+ deviceId: string;
44
+ }) => void;
33
45
  }
34
46
  /** A socket.io `Socket` typed with the secure-chat event maps in both directions. */
35
47
  export type SecureSocket = Socket<SecureServerEvents, SecureClientEvents>;
@@ -43,11 +43,13 @@ export class SecureChatSocketClient {
43
43
  }
44
44
  /** Join a conversation room (membership-gated server-side) to receive its broadcasts. */
45
45
  joinConversation(conversationId) {
46
- this.connect().emit("join:secure-conversation", conversationId);
46
+ // Object payload — the server destructures `{ conversationId }`; a bare string would arrive as
47
+ // `undefined` and the join would silently no-op (see SecureClientEvents).
48
+ this.connect().emit("join:secure-conversation", { conversationId });
47
49
  }
48
50
  /** Explicitly join a device room (ownership-verified). Owned devices auto-join on connect. */
49
51
  joinDevice(deviceId) {
50
- this.connect().emit("join:secure-device", deviceId);
52
+ this.connect().emit("join:secure-device", { deviceId });
51
53
  }
52
54
  /**
53
55
  * Subscribe to a server → client event, auto-connecting if needed.
@@ -1 +1 @@
1
- {"version":3,"file":"socket.js","sourceRoot":"","sources":["../../../src/transport/socket.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,EAAE;AACF,iGAAiG;AACjG,kGAAkG;AAClG,8FAA8F;AAC9F,kCAAkC;AAElC,OAAO,EAAE,EAAE,EAAU,MAAM,kBAAkB,CAAC;AAqC9C;;;;GAIG;AACH,MAAM,OAAO,sBAAsB;IAGjC,YAA6B,MAA8B;QAA9B,WAAM,GAAN,MAAM,CAAwB;QAFnD,WAAM,GAAwB,IAAI,CAAC;IAEmB,CAAC;IAE/D;;;;OAIG;IACH,OAAO;QACL,IAAI,IAAI,CAAC,MAAM,EAAE,SAAS;YAAE,OAAO,IAAI,CAAC,MAAM,CAAC;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC7D,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,GAAG,MAAM,SAAS,EAAE;YACnC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,EAAE;YAC7C,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE;YAC3C,UAAU,EAAE,CAAC,WAAW,CAAC;YACzB,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,kGAAkG;IAClG,UAAU;QACR,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;IACrB,CAAC;IAED,kGAAkG;IAClG,IAAI,GAAG;QACL,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,yFAAyF;IACzF,gBAAgB,CAAC,cAAsB;QACrC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,0BAA0B,EAAE,cAAc,CAAC,CAAC;IAClE,CAAC;IAED,8FAA8F;IAC9F,UAAU,CAAC,QAAgB;QACzB,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAC,CAAC;IACtD,CAAC;IAED;;;;;;OAMG;IACH,EAAE,CAAqC,KAAQ,EAAE,OAA8B;QAC7E,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QACzB,4FAA4F;QAC5F,CAAC,CAAC,EAAE,CAAC,KAAc,EAAE,OAAgB,CAAC,CAAC;QACvC,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAc,EAAE,OAAgB,CAAC,CAAC;IACvD,CAAC;CACF"}
1
+ {"version":3,"file":"socket.js","sourceRoot":"","sources":["../../../src/transport/socket.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,EAAE;AACF,iGAAiG;AACjG,kGAAkG;AAClG,8FAA8F;AAC9F,kCAAkC;AAElC,OAAO,EAAE,EAAE,EAAU,MAAM,kBAAkB,CAAC;AA6C9C;;;;GAIG;AACH,MAAM,OAAO,sBAAsB;IAGjC,YAA6B,MAA8B;QAA9B,WAAM,GAAN,MAAM,CAAwB;QAFnD,WAAM,GAAwB,IAAI,CAAC;IAEmB,CAAC;IAE/D;;;;OAIG;IACH,OAAO;QACL,IAAI,IAAI,CAAC,MAAM,EAAE,SAAS;YAAE,OAAO,IAAI,CAAC,MAAM,CAAC;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC7D,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,GAAG,MAAM,SAAS,EAAE;YACnC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,EAAE;YAC7C,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE;YAC3C,UAAU,EAAE,CAAC,WAAW,CAAC;YACzB,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,kGAAkG;IAClG,UAAU;QACR,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;IACrB,CAAC;IAED,kGAAkG;IAClG,IAAI,GAAG;QACL,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,yFAAyF;IACzF,gBAAgB,CAAC,cAAsB;QACrC,+FAA+F;QAC/F,0EAA0E;QAC1E,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,0BAA0B,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,8FAA8F;IAC9F,UAAU,CAAC,QAAgB;QACzB,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,oBAAoB,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED;;;;;;OAMG;IACH,EAAE,CAAqC,KAAQ,EAAE,OAA8B;QAC7E,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QACzB,4FAA4F;QAC5F,CAAC,CAAC,EAAE,CAAC,KAAc,EAAE,OAAgB,CAAC,CAAC;QACvC,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAc,EAAE,OAAgB,CAAC,CAAC;IACvD,CAAC;CACF"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * How aggressively to pad outbound message plaintext.
3
+ *
4
+ * - `"ladder"` — round the framed length up to the next size bucket (the default; blunts length
5
+ * fingerprinting).
6
+ * - `"none"` — still frame the message (so unpadding stays uniform across clients) but add no extra
7
+ * bytes. Use only when bandwidth matters more than traffic-shape privacy.
8
+ */
9
+ export type PaddingPolicy = "ladder" | "none";
10
+ /**
11
+ * Round a framed byte length up to the next size bucket: the smallest ladder rung
12
+ * (32, 64, … , 8192) that is ≥ `n`, or — above the ladder — the next multiple of 8192.
13
+ *
14
+ * @param n - The unpadded framed length (header + content) in bytes.
15
+ * @returns The bucket size to pad up to (always ≥ 32, always ≥ `n`).
16
+ */
17
+ export declare function nextBucket(n: number): number;
18
+ /**
19
+ * Wrap message content in a size-bucket padding frame for encryption. The output is
20
+ * `[version][contentLen][content][zero pad]`; under `"ladder"` its length is one of the fixed buckets
21
+ * from {@link nextBucket}. Padding bytes are zeros — they ride inside the MLS ciphertext, so they need
22
+ * not be random.
23
+ *
24
+ * @param content - The plaintext bytes to send (e.g. `utf8ToBytes(text)`).
25
+ * @param policy - The {@link PaddingPolicy}; defaults to `"ladder"`.
26
+ * @returns The framed, padded bytes to hand to `crypto.encryptMessage`.
27
+ */
28
+ export declare function padPlaintext(content: Uint8Array, policy?: PaddingPolicy): Uint8Array;
29
+ /**
30
+ * Recover the original content from a {@link padPlaintext} frame after decryption. Validates the version
31
+ * byte and bounds-checks the declared length, then slices off the header and trailing zero padding.
32
+ *
33
+ * @param frame - The decrypted frame bytes (from `crypto.decryptMessage`).
34
+ * @returns The original content bytes.
35
+ * @throws {Error} On a frame that is too short, carries an unknown version, or declares a length that
36
+ * overruns the buffer — a successfully-authenticated MLS message with a bad frame is a real
37
+ * framing/version mismatch, so the caller fails closed rather than rendering raw padded bytes.
38
+ */
39
+ export declare function unpadPlaintext(frame: Uint8Array): Uint8Array;
@@ -0,0 +1,75 @@
1
+ // Message size-bucket padding — a metadata-hardening frame applied to plaintext BEFORE MLS encryption.
2
+ //
3
+ // Where it sits in the blind-server model: the server (and any DB/network observer) is trusted to relay
4
+ // ciphertext but learns *envelope* metadata in the Signal model — including ciphertext SIZE. Raw chat
5
+ // text encrypts to a ciphertext whose length tracks the message length, leaking traffic shape. We wrap
6
+ // the plaintext in a self-describing frame and zero-pad it up to a fixed bucket ladder, so short
7
+ // messages collapse into a few sizes. MLS then encrypts the frame, so the ciphertext inherits the
8
+ // bucketing (modulo a constant MLS framing overhead). This is pure byte-shuffling — NO crypto, no keys
9
+ // — and the frame is symmetric: every client pads on send and unpads on receive identically.
10
+ //
11
+ // Frame layout: [version:1B = 1][contentLen:uint32 BE][content bytes][zero padding → bucket].
12
+ /** The smallest bucket; also the floor for an empty message (a 5-byte header → bucket 32). */
13
+ const LADDER = [32, 64, 128, 256, 512, 1024, 2048, 4096, 8192];
14
+ /** Above the ladder we round up to multiples of this (the largest ladder rung). */
15
+ const STEP = 8192;
16
+ /** Frame header: 1 version byte + a 4-byte big-endian content length. */
17
+ const HEADER = 5;
18
+ /** Frame format version, bound as the first byte so the codec can evolve without ambiguity. */
19
+ const VERSION = 1;
20
+ /**
21
+ * Round a framed byte length up to the next size bucket: the smallest ladder rung
22
+ * (32, 64, … , 8192) that is ≥ `n`, or — above the ladder — the next multiple of 8192.
23
+ *
24
+ * @param n - The unpadded framed length (header + content) in bytes.
25
+ * @returns The bucket size to pad up to (always ≥ 32, always ≥ `n`).
26
+ */
27
+ export function nextBucket(n) {
28
+ for (const b of LADDER)
29
+ if (n <= b)
30
+ return b;
31
+ return Math.ceil(n / STEP) * STEP;
32
+ }
33
+ /**
34
+ * Wrap message content in a size-bucket padding frame for encryption. The output is
35
+ * `[version][contentLen][content][zero pad]`; under `"ladder"` its length is one of the fixed buckets
36
+ * from {@link nextBucket}. Padding bytes are zeros — they ride inside the MLS ciphertext, so they need
37
+ * not be random.
38
+ *
39
+ * @param content - The plaintext bytes to send (e.g. `utf8ToBytes(text)`).
40
+ * @param policy - The {@link PaddingPolicy}; defaults to `"ladder"`.
41
+ * @returns The framed, padded bytes to hand to `crypto.encryptMessage`.
42
+ */
43
+ export function padPlaintext(content, policy = "ladder") {
44
+ const framed = HEADER + content.length;
45
+ const target = policy === "ladder" ? nextBucket(framed) : framed;
46
+ const out = new Uint8Array(target); // zero-filled → the padding is already in place
47
+ out[0] = VERSION;
48
+ out[1] = (content.length >>> 24) & 0xff;
49
+ out[2] = (content.length >>> 16) & 0xff;
50
+ out[3] = (content.length >>> 8) & 0xff;
51
+ out[4] = content.length & 0xff;
52
+ out.set(content, HEADER);
53
+ return out;
54
+ }
55
+ /**
56
+ * Recover the original content from a {@link padPlaintext} frame after decryption. Validates the version
57
+ * byte and bounds-checks the declared length, then slices off the header and trailing zero padding.
58
+ *
59
+ * @param frame - The decrypted frame bytes (from `crypto.decryptMessage`).
60
+ * @returns The original content bytes.
61
+ * @throws {Error} On a frame that is too short, carries an unknown version, or declares a length that
62
+ * overruns the buffer — a successfully-authenticated MLS message with a bad frame is a real
63
+ * framing/version mismatch, so the caller fails closed rather than rendering raw padded bytes.
64
+ */
65
+ export function unpadPlaintext(frame) {
66
+ if (frame.length < HEADER)
67
+ throw new Error("secure-chat: padding frame too short");
68
+ if (frame[0] !== VERSION)
69
+ throw new Error(`secure-chat: unsupported padding frame version ${frame[0]}`);
70
+ const len = (frame[1] << 24) | (frame[2] << 16) | (frame[3] << 8) | frame[4];
71
+ if (len < 0 || HEADER + len > frame.length)
72
+ throw new Error("secure-chat: padding frame length out of range");
73
+ return frame.slice(HEADER, HEADER + len);
74
+ }
75
+ //# sourceMappingURL=padding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"padding.js","sourceRoot":"","sources":["../../../src/util/padding.ts"],"names":[],"mappings":"AAAA,uGAAuG;AACvG,EAAE;AACF,wGAAwG;AACxG,sGAAsG;AACtG,uGAAuG;AACvG,iGAAiG;AACjG,kGAAkG;AAClG,uGAAuG;AACvG,6FAA6F;AAC7F,EAAE;AACF,8FAA8F;AAE9F,8FAA8F;AAC9F,MAAM,MAAM,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAU,CAAC;AACxE,mFAAmF;AACnF,MAAM,IAAI,GAAG,IAAI,CAAC;AAClB,yEAAyE;AACzE,MAAM,MAAM,GAAG,CAAC,CAAC;AACjB,+FAA+F;AAC/F,MAAM,OAAO,GAAG,CAAC,CAAC;AAYlB;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,KAAK,MAAM,CAAC,IAAI,MAAM;QAAE,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,CAAC,CAAC;IAC7C,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;AACpC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,OAAmB,EAAE,SAAwB,QAAQ;IAChF,MAAM,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IACvC,MAAM,MAAM,GAAG,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACjE,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,gDAAgD;IACpF,GAAG,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC;IACjB,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IACxC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IACxC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC;IACvC,GAAG,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAC/B,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACzB,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,KAAiB;IAC9C,IAAI,KAAK,CAAC,MAAM,GAAG,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IACnF,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,kDAAkD,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACxG,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7E,IAAI,GAAG,GAAG,CAAC,IAAI,MAAM,GAAG,GAAG,GAAG,KAAK,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IAC9G,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,27 @@
1
+ import type { GroupMemberIdentity } from "@agora-sdk/secure-chat-crypto";
2
+ /** A derived safety number, in the forms a UI needs. */
3
+ export interface SafetyNumber {
4
+ /** All 60 decimal digits with no separators. */
5
+ digits: string;
6
+ /** The 60 digits as 12 groups of 5 (for display / read-aloud). */
7
+ groups: string[];
8
+ /** The raw combined fingerprint bytes (sorted-concatenated per-party hashes) — e.g. for a QR code. */
9
+ fingerprint: Uint8Array;
10
+ }
11
+ /**
12
+ * Derive the safety number for two parties from their public identities. The result is **symmetric** —
13
+ * `computeSafetyNumber(a, b)` equals `computeSafetyNumber(b, a)` — because the two per-party
14
+ * fingerprints are sorted before concatenation. Uses WebCrypto SHA-256 (available in browsers and
15
+ * Node 18+).
16
+ *
17
+ * @param a - One party's `{ deviceId, signaturePublicKey }` (e.g. the local device).
18
+ * @param b - The other party's identity (e.g. the remote peer).
19
+ * @returns The {@link SafetyNumber} (60 digits, 12 groups of 5, plus raw fingerprint bytes).
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const { groups } = await computeSafetyNumber(localIdentity, peerIdentity);
24
+ * // groups → ["12345", "67890", … 12 of them]; compare with the peer out-of-band.
25
+ * ```
26
+ */
27
+ export declare function computeSafetyNumber(a: GroupMemberIdentity, b: GroupMemberIdentity): Promise<SafetyNumber>;
@@ -0,0 +1,81 @@
1
+ // Safety number — a human-comparable fingerprint of two devices' identity keys (key verification).
2
+ //
3
+ // Where it sits in the blind-server model: the server is blind but UNTRUSTED, so it could in principle
4
+ // hand a victim a KeyPackage carrying an attacker's signature key (a classic active MITM on a TOFU
5
+ // system). A safety number lets two users confirm out-of-band (read aloud / compare on screen / scan a
6
+ // QR) that they actually hold each other's real identity keys. It is derived ONLY from public
7
+ // signature keys + device ids — no secrets — and is symmetric: both sides compute the same number.
8
+ //
9
+ // Shape (Signal-style): each party's identity is hashed into a 30-digit number; the two are sorted (so
10
+ // the result is order-independent) and concatenated into 60 decimal digits, shown as 12 groups of 5.
11
+ /** Format/version byte bound into the hash so the derivation can evolve unambiguously. */
12
+ const VERSION = 0;
13
+ /** Hash iterations per party. Public-key fingerprints don't need KDF-grade work; this is a fixed,
14
+ * app-wide constant purely so every Agora client derives an identical number (interop, not secrecy). */
15
+ const ITERATIONS = 1024;
16
+ /** Bytes of the final hash consumed: 6 chunks × 5 bytes → 6 × 5 digits = 30 digits per party. */
17
+ const CHUNKS = 6;
18
+ const CHUNK_BYTES = 5;
19
+ const utf8 = (s) => new TextEncoder().encode(s);
20
+ function concat(...parts) {
21
+ const total = parts.reduce((n, p) => n + p.length, 0);
22
+ const out = new Uint8Array(total);
23
+ let off = 0;
24
+ for (const p of parts) {
25
+ out.set(p, off);
26
+ off += p.length;
27
+ }
28
+ return out;
29
+ }
30
+ async function sha256(bytes) {
31
+ // Cast to BufferSource: our Uint8Array is always ArrayBuffer-backed, but the lib type is the wider
32
+ // Uint8Array<ArrayBufferLike> (which could be SharedArrayBuffer) that digest() won't accept directly.
33
+ return new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", bytes));
34
+ }
35
+ /** Encode one 5-byte chunk as a 5-digit decimal string (40-bit big-endian value mod 100000). */
36
+ function chunkToDigits(hash, offset) {
37
+ // 2**40 < 2**53, so plain Number arithmetic is exact here.
38
+ let value = 0;
39
+ for (let i = 0; i < CHUNK_BYTES; i++)
40
+ value = value * 256 + hash[offset + i];
41
+ return (value % 100000).toString().padStart(5, "0");
42
+ }
43
+ /** Per-party fingerprint: an iterated hash of (version || pubkey || deviceId), then 30 digits + the
44
+ * truncated hash bytes. Signal-shaped (the key is re-mixed in each round). */
45
+ async function partyFingerprint(party) {
46
+ const key = party.signaturePublicKey;
47
+ let hash = concat(Uint8Array.of(VERSION), key, utf8(party.deviceId));
48
+ for (let i = 0; i < ITERATIONS; i++)
49
+ hash = await sha256(concat(hash, key));
50
+ let digits = "";
51
+ for (let c = 0; c < CHUNKS; c++)
52
+ digits += chunkToDigits(hash, c * CHUNK_BYTES);
53
+ return { digits, bytes: hash.slice(0, CHUNKS * CHUNK_BYTES) };
54
+ }
55
+ /**
56
+ * Derive the safety number for two parties from their public identities. The result is **symmetric** —
57
+ * `computeSafetyNumber(a, b)` equals `computeSafetyNumber(b, a)` — because the two per-party
58
+ * fingerprints are sorted before concatenation. Uses WebCrypto SHA-256 (available in browsers and
59
+ * Node 18+).
60
+ *
61
+ * @param a - One party's `{ deviceId, signaturePublicKey }` (e.g. the local device).
62
+ * @param b - The other party's identity (e.g. the remote peer).
63
+ * @returns The {@link SafetyNumber} (60 digits, 12 groups of 5, plus raw fingerprint bytes).
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * const { groups } = await computeSafetyNumber(localIdentity, peerIdentity);
68
+ * // groups → ["12345", "67890", … 12 of them]; compare with the peer out-of-band.
69
+ * ```
70
+ */
71
+ export async function computeSafetyNumber(a, b) {
72
+ const [fpA, fpB] = await Promise.all([partyFingerprint(a), partyFingerprint(b)]);
73
+ // Sort so the number is the same regardless of who is "a" vs "b".
74
+ const [first, second] = fpA.digits <= fpB.digits ? [fpA, fpB] : [fpB, fpA];
75
+ const digits = first.digits + second.digits;
76
+ const groups = [];
77
+ for (let i = 0; i < digits.length; i += 5)
78
+ groups.push(digits.slice(i, i + 5));
79
+ return { digits, groups, fingerprint: concat(first.bytes, second.bytes) };
80
+ }
81
+ //# sourceMappingURL=safety-number.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safety-number.js","sourceRoot":"","sources":["../../../src/util/safety-number.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agora-sdk/secure-chat-core",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "author": "Agora SDK Plus, maintained by Jenova Marie",
@@ -35,9 +35,10 @@
35
35
  "dist"
36
36
  ],
37
37
  "dependencies": {
38
+ "@agora-server/contract": "^0.9.3",
38
39
  "axios": "^1.4.0",
39
40
  "socket.io-client": "^4.8.1",
40
- "@agora-sdk/secure-chat-crypto": "0.3.0"
41
+ "@agora-sdk/secure-chat-crypto": "0.5.0"
41
42
  },
42
43
  "peerDependencies": {
43
44
  "@agora-sdk/core": "^1.2.2",