@octomil/browser 1.0.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/LICENSE +21 -0
  2. package/README.md +75 -0
  3. package/dist/cache.d.ts +25 -0
  4. package/dist/cache.d.ts.map +1 -0
  5. package/dist/cache.js +202 -0
  6. package/dist/cache.js.map +1 -0
  7. package/dist/device-auth.d.ts +41 -0
  8. package/dist/device-auth.d.ts.map +1 -0
  9. package/dist/device-auth.js +203 -0
  10. package/dist/device-auth.js.map +1 -0
  11. package/dist/experiments.d.ts +44 -0
  12. package/dist/experiments.d.ts.map +1 -0
  13. package/dist/experiments.js +135 -0
  14. package/dist/experiments.js.map +1 -0
  15. package/dist/federated.d.ts +53 -0
  16. package/dist/federated.d.ts.map +1 -0
  17. package/dist/federated.js +180 -0
  18. package/dist/federated.js.map +1 -0
  19. package/dist/index.cjs +2148 -0
  20. package/dist/index.cjs.map +7 -0
  21. package/dist/index.d.ts +37 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +45 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/inference.d.ts +43 -0
  26. package/dist/inference.d.ts.map +1 -0
  27. package/dist/inference.js +213 -0
  28. package/dist/inference.js.map +1 -0
  29. package/dist/integrity.d.ts +19 -0
  30. package/dist/integrity.d.ts.map +1 -0
  31. package/dist/integrity.js +35 -0
  32. package/dist/integrity.js.map +1 -0
  33. package/dist/model-loader.d.ts +40 -0
  34. package/dist/model-loader.d.ts.map +1 -0
  35. package/dist/model-loader.js +232 -0
  36. package/dist/model-loader.js.map +1 -0
  37. package/dist/octomil.d.ts +92 -0
  38. package/dist/octomil.d.ts.map +1 -0
  39. package/dist/octomil.js +368 -0
  40. package/dist/octomil.js.map +1 -0
  41. package/dist/octomil.min.js +2849 -0
  42. package/dist/octomil.min.js.map +7 -0
  43. package/dist/privacy.d.ts +40 -0
  44. package/dist/privacy.d.ts.map +1 -0
  45. package/dist/privacy.js +118 -0
  46. package/dist/privacy.js.map +1 -0
  47. package/dist/rollouts.d.ts +43 -0
  48. package/dist/rollouts.d.ts.map +1 -0
  49. package/dist/rollouts.js +114 -0
  50. package/dist/rollouts.js.map +1 -0
  51. package/dist/secure-aggregation.d.ts +50 -0
  52. package/dist/secure-aggregation.d.ts.map +1 -0
  53. package/dist/secure-aggregation.js +174 -0
  54. package/dist/secure-aggregation.js.map +1 -0
  55. package/dist/streaming.d.ts +25 -0
  56. package/dist/streaming.d.ts.map +1 -0
  57. package/dist/streaming.js +148 -0
  58. package/dist/streaming.js.map +1 -0
  59. package/dist/telemetry.d.ts +41 -0
  60. package/dist/telemetry.d.ts.map +1 -0
  61. package/dist/telemetry.js +130 -0
  62. package/dist/telemetry.js.map +1 -0
  63. package/dist/types.d.ts +239 -0
  64. package/dist/types.d.ts.map +1 -0
  65. package/dist/types.js +17 -0
  66. package/dist/types.js.map +1 -0
  67. package/package.json +62 -0
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @octomil/browser — Privacy filters
3
+ *
4
+ * Differential privacy (gradient clipping + noise injection) and
5
+ * quantization for communication-efficient federated learning.
6
+ */
7
+ import type { WeightMap } from "./types.js";
8
+ /**
9
+ * Clip gradients by L2 norm. If the L2 norm of the flattened weight map
10
+ * exceeds `maxNorm`, scale all values down proportionally.
11
+ */
12
+ export declare function clipGradients(delta: WeightMap, maxNorm: number): WeightMap;
13
+ /**
14
+ * Add calibrated Gaussian noise for (epsilon, delta)-differential privacy.
15
+ *
16
+ * Noise std = sensitivity * sqrt(2 * ln(1.25/deltaDP)) / epsilon
17
+ *
18
+ * @param delta Weight deltas to perturb.
19
+ * @param epsilon Privacy budget.
20
+ * @param sensitivity L2 sensitivity (typically the clipping norm).
21
+ * @param deltaDP DP delta parameter.
22
+ */
23
+ export declare function addGaussianNoise(delta: WeightMap, epsilon: number, sensitivity: number, deltaDP: number): WeightMap;
24
+ export interface QuantizedWeightMap {
25
+ [key: string]: {
26
+ data: Int8Array | Int16Array;
27
+ scale: number;
28
+ zeroPoint: number;
29
+ };
30
+ }
31
+ /**
32
+ * Quantize weights to reduced precision (8 or 16 bit).
33
+ * Uses min-max symmetric quantization.
34
+ */
35
+ export declare function quantize(delta: WeightMap, bits?: 8 | 16): QuantizedWeightMap;
36
+ /**
37
+ * Dequantize back to Float32Array.
38
+ */
39
+ export declare function dequantize(quantized: QuantizedWeightMap): WeightMap;
40
+ //# sourceMappingURL=privacy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"privacy.d.ts","sourceRoot":"","sources":["../src/privacy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAM5C;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAyB1E;AAMD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,GACd,SAAS,CAaX;AAiBD,MAAM,WAAW,kBAAkB;IACjC,CAAC,GAAG,EAAE,MAAM,GAAG;QACb,IAAI,EAAE,SAAS,GAAG,UAAU,CAAC;QAC7B,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,SAAS,EAChB,IAAI,GAAE,CAAC,GAAG,EAAM,GACf,kBAAkB,CAwBpB;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,kBAAkB,GAAG,SAAS,CAWnE"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * @octomil/browser — Privacy filters
3
+ *
4
+ * Differential privacy (gradient clipping + noise injection) and
5
+ * quantization for communication-efficient federated learning.
6
+ */
7
+ // ---------------------------------------------------------------------------
8
+ // Gradient Clipping
9
+ // ---------------------------------------------------------------------------
10
+ /**
11
+ * Clip gradients by L2 norm. If the L2 norm of the flattened weight map
12
+ * exceeds `maxNorm`, scale all values down proportionally.
13
+ */
14
+ export function clipGradients(delta, maxNorm) {
15
+ let sumSq = 0;
16
+ for (const arr of Object.values(delta)) {
17
+ if (!arr)
18
+ continue;
19
+ for (let i = 0; i < arr.length; i++) {
20
+ sumSq += arr[i] * arr[i];
21
+ }
22
+ }
23
+ const norm = Math.sqrt(sumSq);
24
+ if (norm <= maxNorm) {
25
+ return delta; // No clipping needed
26
+ }
27
+ const scale = maxNorm / norm;
28
+ const clipped = {};
29
+ for (const [key, arr] of Object.entries(delta)) {
30
+ if (!arr)
31
+ continue;
32
+ const c = new Float32Array(arr.length);
33
+ for (let i = 0; i < arr.length; i++) {
34
+ c[i] = arr[i] * scale;
35
+ }
36
+ clipped[key] = c;
37
+ }
38
+ return clipped;
39
+ }
40
+ // ---------------------------------------------------------------------------
41
+ // Gaussian Noise Injection
42
+ // ---------------------------------------------------------------------------
43
+ /**
44
+ * Add calibrated Gaussian noise for (epsilon, delta)-differential privacy.
45
+ *
46
+ * Noise std = sensitivity * sqrt(2 * ln(1.25/deltaDP)) / epsilon
47
+ *
48
+ * @param delta Weight deltas to perturb.
49
+ * @param epsilon Privacy budget.
50
+ * @param sensitivity L2 sensitivity (typically the clipping norm).
51
+ * @param deltaDP DP delta parameter.
52
+ */
53
+ export function addGaussianNoise(delta, epsilon, sensitivity, deltaDP) {
54
+ const sigma = (sensitivity * Math.sqrt(2 * Math.log(1.25 / deltaDP))) / epsilon;
55
+ const noisy = {};
56
+ for (const [key, arr] of Object.entries(delta)) {
57
+ if (!arr)
58
+ continue;
59
+ const n = new Float32Array(arr.length);
60
+ for (let i = 0; i < arr.length; i++) {
61
+ n[i] = arr[i] + gaussianRandom() * sigma;
62
+ }
63
+ noisy[key] = n;
64
+ }
65
+ return noisy;
66
+ }
67
+ /** Box-Muller transform for generating Gaussian random numbers. */
68
+ function gaussianRandom() {
69
+ let u1;
70
+ let u2;
71
+ do {
72
+ u1 = Math.random();
73
+ u2 = Math.random();
74
+ } while (u1 === 0);
75
+ return Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2);
76
+ }
77
+ /**
78
+ * Quantize weights to reduced precision (8 or 16 bit).
79
+ * Uses min-max symmetric quantization.
80
+ */
81
+ export function quantize(delta, bits = 8) {
82
+ const maxVal = bits === 8 ? 127 : 32767;
83
+ const result = {};
84
+ for (const [key, arr] of Object.entries(delta)) {
85
+ if (!arr)
86
+ continue;
87
+ let absMax = 0;
88
+ for (let i = 0; i < arr.length; i++) {
89
+ const abs = Math.abs(arr[i]);
90
+ if (abs > absMax)
91
+ absMax = abs;
92
+ }
93
+ const scale = absMax > 0 ? absMax / maxVal : 1;
94
+ const quantized = bits === 8 ? new Int8Array(arr.length) : new Int16Array(arr.length);
95
+ for (let i = 0; i < arr.length; i++) {
96
+ quantized[i] = Math.round(arr[i] / scale);
97
+ }
98
+ result[key] = { data: quantized, scale, zeroPoint: 0 };
99
+ }
100
+ return result;
101
+ }
102
+ /**
103
+ * Dequantize back to Float32Array.
104
+ */
105
+ export function dequantize(quantized) {
106
+ const result = {};
107
+ for (const [key, entry] of Object.entries(quantized)) {
108
+ if (!entry)
109
+ continue;
110
+ const arr = new Float32Array(entry.data.length);
111
+ for (let i = 0; i < entry.data.length; i++) {
112
+ arr[i] = (entry.data[i] - entry.zeroPoint) * entry.scale;
113
+ }
114
+ result[key] = arr;
115
+ }
116
+ return result;
117
+ }
118
+ //# sourceMappingURL=privacy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"privacy.js","sourceRoot":"","sources":["../src/privacy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @octomil/browser — Rollout and canary management
3
+ *
4
+ * Resolves which model version a device should use based on server-side
5
+ * rollout configuration. Uses deterministic hashing for stable canary
6
+ * group assignment.
7
+ */
8
+ import type { RolloutConfig, RolloutVersion, TelemetryEvent } from "./types.js";
9
+ export declare class RolloutsManager {
10
+ private readonly serverUrl;
11
+ private readonly apiKey?;
12
+ private readonly cacheTtlMs;
13
+ private readonly onTelemetry?;
14
+ private configCache;
15
+ constructor(options: {
16
+ serverUrl: string;
17
+ apiKey?: string;
18
+ cacheTtlMs?: number;
19
+ onTelemetry?: (event: TelemetryEvent) => void;
20
+ });
21
+ /**
22
+ * Resolve which version a device should use.
23
+ *
24
+ * Logic:
25
+ * 1. If a canary version exists, check if device is in canary group.
26
+ * 2. Otherwise return the active version.
27
+ */
28
+ resolveVersion(modelId: string, deviceId: string): Promise<string>;
29
+ /** Fetch rollout configuration, with caching. */
30
+ getRolloutConfig(modelId: string): Promise<RolloutConfig>;
31
+ /**
32
+ * Deterministic check: is this device in the canary group?
33
+ * Uses a simple hash of (deviceId + modelId) to assign a 0-99 bucket.
34
+ */
35
+ isInCanaryGroup(modelId: string, deviceId: string, canaryPercentage: number): boolean;
36
+ /** Get all available versions for a model. */
37
+ getAvailableVersions(modelId: string): Promise<RolloutVersion[]>;
38
+ /** Report rollout success/failure to the server. */
39
+ reportRolloutStatus(modelId: string, version: string, status: "success" | "failure"): Promise<void>;
40
+ /** Clear the rollout config cache. */
41
+ clearCache(): void;
42
+ }
43
+ //# sourceMappingURL=rollouts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rollouts.d.ts","sourceRoot":"","sources":["../src/rollouts.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAOhF,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAkC;IAE/D,OAAO,CAAC,WAAW,CAGf;gBAEQ,OAAO,EAAE;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;KAC/C;IAOD;;;;;;OAMG;IACG,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAmBxE,iDAAiD;IAC3C,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAuB/D;;;OAGG;IACH,eAAe,CACb,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,gBAAgB,EAAE,MAAM,GACvB,OAAO;IAKV,8CAA8C;IACxC,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAKtE,oDAAoD;IAC9C,mBAAmB,CACvB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,SAAS,GAAG,SAAS,GAC5B,OAAO,CAAC,IAAI,CAAC;IAqBhB,sCAAsC;IACtC,UAAU,IAAI,IAAI;CAGnB"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * @octomil/browser — Rollout and canary management
3
+ *
4
+ * Resolves which model version a device should use based on server-side
5
+ * rollout configuration. Uses deterministic hashing for stable canary
6
+ * group assignment.
7
+ */
8
+ import { OctomilError } from "./types.js";
9
+ // ---------------------------------------------------------------------------
10
+ // RolloutsManager
11
+ // ---------------------------------------------------------------------------
12
+ export class RolloutsManager {
13
+ serverUrl;
14
+ apiKey;
15
+ cacheTtlMs;
16
+ onTelemetry;
17
+ configCache = new Map();
18
+ constructor(options) {
19
+ this.serverUrl = options.serverUrl;
20
+ this.apiKey = options.apiKey;
21
+ this.cacheTtlMs = options.cacheTtlMs ?? 5 * 60 * 1000; // 5 min
22
+ this.onTelemetry = options.onTelemetry;
23
+ }
24
+ /**
25
+ * Resolve which version a device should use.
26
+ *
27
+ * Logic:
28
+ * 1. If a canary version exists, check if device is in canary group.
29
+ * 2. Otherwise return the active version.
30
+ */
31
+ async resolveVersion(modelId, deviceId) {
32
+ const config = await this.getRolloutConfig(modelId);
33
+ // Check for canary version
34
+ const canary = config.versions.find((v) => v.status === "canary");
35
+ if (canary && this.isInCanaryGroup(modelId, deviceId, canary.percentage)) {
36
+ return canary.version;
37
+ }
38
+ // Fall back to active version
39
+ const active = config.versions.find((v) => v.status === "active");
40
+ if (active)
41
+ return active.version;
42
+ throw new OctomilError("MODEL_NOT_FOUND", `No active version found for model "${modelId}".`);
43
+ }
44
+ /** Fetch rollout configuration, with caching. */
45
+ async getRolloutConfig(modelId) {
46
+ const cached = this.configCache.get(modelId);
47
+ if (cached && Date.now() - cached.fetchedAt < this.cacheTtlMs) {
48
+ return cached.config;
49
+ }
50
+ const url = `${this.serverUrl}/api/v1/models/${encodeURIComponent(modelId)}/rollout`;
51
+ const headers = { Accept: "application/json" };
52
+ if (this.apiKey)
53
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
54
+ const response = await fetch(url, { headers });
55
+ if (!response.ok) {
56
+ throw new OctomilError("NETWORK_ERROR", `Failed to fetch rollout config: HTTP ${response.status}`);
57
+ }
58
+ const config = (await response.json());
59
+ this.configCache.set(modelId, { config, fetchedAt: Date.now() });
60
+ return config;
61
+ }
62
+ /**
63
+ * Deterministic check: is this device in the canary group?
64
+ * Uses a simple hash of (deviceId + modelId) to assign a 0-99 bucket.
65
+ */
66
+ isInCanaryGroup(modelId, deviceId, canaryPercentage) {
67
+ const bucket = deterministicBucket(deviceId + modelId);
68
+ return bucket < canaryPercentage;
69
+ }
70
+ /** Get all available versions for a model. */
71
+ async getAvailableVersions(modelId) {
72
+ const config = await this.getRolloutConfig(modelId);
73
+ return config.versions;
74
+ }
75
+ /** Report rollout success/failure to the server. */
76
+ async reportRolloutStatus(modelId, version, status) {
77
+ const url = `${this.serverUrl}/api/v1/models/${encodeURIComponent(modelId)}/rollout/status`;
78
+ const headers = {
79
+ "Content-Type": "application/json",
80
+ };
81
+ if (this.apiKey)
82
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
83
+ await fetch(url, {
84
+ method: "POST",
85
+ headers,
86
+ body: JSON.stringify({ version, status }),
87
+ });
88
+ this.onTelemetry?.({
89
+ type: "rollout_status",
90
+ model: modelId,
91
+ metadata: { version, status },
92
+ timestamp: Date.now(),
93
+ });
94
+ }
95
+ /** Clear the rollout config cache. */
96
+ clearCache() {
97
+ this.configCache.clear();
98
+ }
99
+ }
100
+ // ---------------------------------------------------------------------------
101
+ // Helpers
102
+ // ---------------------------------------------------------------------------
103
+ /**
104
+ * Simple deterministic hash → bucket [0, 100).
105
+ * Uses djb2 hash for speed (no crypto needed for bucketing).
106
+ */
107
+ function deterministicBucket(input) {
108
+ let hash = 5381;
109
+ for (let i = 0; i < input.length; i++) {
110
+ hash = ((hash << 5) + hash + input.charCodeAt(i)) | 0;
111
+ }
112
+ return Math.abs(hash) % 100;
113
+ }
114
+ //# sourceMappingURL=rollouts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rollouts.js","sourceRoot":"","sources":["../src/rollouts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @octomil/browser — Secure aggregation (SecAgg / SecAgg+)
3
+ *
4
+ * Implements pairwise masking using ECDH key exchange and Shamir's
5
+ * secret sharing, all via the Web Crypto API. Ensures the server
6
+ * only sees the aggregate of client updates, never individual deltas.
7
+ */
8
+ import type { WeightMap } from "./types.js";
9
+ export declare class SecureAggregation {
10
+ private keyPair;
11
+ /** Generate an ECDH key pair for this round. */
12
+ generateKeyPair(): Promise<{
13
+ publicKey: JsonWebKey;
14
+ }>;
15
+ /** Derive a shared secret with a peer using ECDH. */
16
+ deriveSharedSecret(peerPublicKeyJwk: JsonWebKey): Promise<ArrayBuffer>;
17
+ /**
18
+ * Generate a deterministic PRG mask from a shared secret.
19
+ * Uses the secret as a seed to produce `length` float values.
20
+ */
21
+ createMask(secret: ArrayBuffer, length: number): Promise<Float32Array>;
22
+ /** Add masks to a weight update: masked = delta + sum(masks). */
23
+ maskUpdate(delta: WeightMap, masks: Map<string, Float32Array>): WeightMap;
24
+ /** Remove masks of dropped peers from the aggregated sum. */
25
+ unmask(maskedSum: WeightMap, droppedMasks: Map<string, Float32Array>): WeightMap;
26
+ }
27
+ export interface SecretShare {
28
+ x: number;
29
+ y: number;
30
+ }
31
+ /**
32
+ * Split a secret into `numShares` shares requiring `threshold` to reconstruct.
33
+ */
34
+ export declare function shamirSplit(secret: number, threshold: number, numShares: number): SecretShare[];
35
+ /**
36
+ * Reconstruct a secret from `threshold` shares via Lagrange interpolation.
37
+ */
38
+ export declare function shamirReconstruct(shares: SecretShare[]): number;
39
+ export declare class SecAggPlus extends SecureAggregation {
40
+ private readonly threshold;
41
+ constructor(threshold: number);
42
+ /**
43
+ * Split a shared secret into Shamir shares so that any `threshold`
44
+ * surviving peers can reconstruct the mask of a dropped peer.
45
+ */
46
+ splitSecret(secret: number, numPeers: number): SecretShare[];
47
+ /** Reconstruct a dropped peer's secret from collected shares. */
48
+ reconstructSecret(shares: SecretShare[]): number;
49
+ }
50
+ //# sourceMappingURL=secure-aggregation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secure-aggregation.d.ts","sourceRoot":"","sources":["../src/secure-aggregation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAM5C,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,OAAO,CAA8B;IAE7C,gDAAgD;IAC1C,eAAe,IAAI,OAAO,CAAC;QAAE,SAAS,EAAE,UAAU,CAAA;KAAE,CAAC;IAa3D,qDAAqD;IAC/C,kBAAkB,CAAC,gBAAgB,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC;IAkB5E;;;OAGG;IACG,UAAU,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IA+B5E,iEAAiE;IACjE,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,SAAS;IAgBzE,6DAA6D;IAC7D,MAAM,CACJ,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,GACtC,SAAS;CAeb;AA0BD,MAAM,WAAW,WAAW;IAC1B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,WAAW,EAAE,CAqBf;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CA4B/D;AAMD,qBAAa,UAAW,SAAQ,iBAAiB;IAC/C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,SAAS,EAAE,MAAM;IAK7B;;;OAGG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,EAAE;IAI5D,iEAAiE;IACjE,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM;CAQjD"}
@@ -0,0 +1,174 @@
1
+ /**
2
+ * @octomil/browser — Secure aggregation (SecAgg / SecAgg+)
3
+ *
4
+ * Implements pairwise masking using ECDH key exchange and Shamir's
5
+ * secret sharing, all via the Web Crypto API. Ensures the server
6
+ * only sees the aggregate of client updates, never individual deltas.
7
+ */
8
+ // ---------------------------------------------------------------------------
9
+ // SecureAggregation (basic pairwise masking)
10
+ // ---------------------------------------------------------------------------
11
+ export class SecureAggregation {
12
+ keyPair = null;
13
+ /** Generate an ECDH key pair for this round. */
14
+ async generateKeyPair() {
15
+ this.keyPair = await crypto.subtle.generateKey({ name: "ECDH", namedCurve: "P-256" }, true, ["deriveBits"]);
16
+ const publicKey = await crypto.subtle.exportKey("jwk", this.keyPair.publicKey);
17
+ return { publicKey };
18
+ }
19
+ /** Derive a shared secret with a peer using ECDH. */
20
+ async deriveSharedSecret(peerPublicKeyJwk) {
21
+ if (!this.keyPair) {
22
+ throw new Error("Call generateKeyPair() first.");
23
+ }
24
+ const peerKey = await crypto.subtle.importKey("jwk", peerPublicKeyJwk, { name: "ECDH", namedCurve: "P-256" }, false, []);
25
+ return crypto.subtle.deriveBits({ name: "ECDH", public: peerKey }, this.keyPair.privateKey, 256);
26
+ }
27
+ /**
28
+ * Generate a deterministic PRG mask from a shared secret.
29
+ * Uses the secret as a seed to produce `length` float values.
30
+ */
31
+ async createMask(secret, length) {
32
+ // Expand seed via HKDF-SHA256, then interpret as float offsets
33
+ const keyMaterial = await crypto.subtle.importKey("raw", secret, "HKDF", false, ["deriveBits"]);
34
+ const bitsNeeded = length * 4 * 8; // Float32 = 4 bytes
35
+ const bits = await crypto.subtle.deriveBits({
36
+ name: "HKDF",
37
+ hash: "SHA-256",
38
+ salt: new Uint8Array(32),
39
+ info: new TextEncoder().encode("octomil-secagg-mask"),
40
+ }, keyMaterial, Math.min(bitsNeeded, 8160));
41
+ // If we need more bits than one deriveBits call can give,
42
+ // tile the output.
43
+ const mask = new Float32Array(length);
44
+ const source = new Float32Array(bits);
45
+ for (let i = 0; i < length; i++) {
46
+ mask[i] = source[i % source.length];
47
+ }
48
+ return mask;
49
+ }
50
+ /** Add masks to a weight update: masked = delta + sum(masks). */
51
+ maskUpdate(delta, masks) {
52
+ const masked = {};
53
+ for (const [key, arr] of Object.entries(delta)) {
54
+ if (!arr)
55
+ continue;
56
+ const result = new Float32Array(arr);
57
+ const mask = masks.get(key);
58
+ if (mask) {
59
+ for (let i = 0; i < result.length; i++) {
60
+ result[i] = result[i] + (mask[i] ?? 0);
61
+ }
62
+ }
63
+ masked[key] = result;
64
+ }
65
+ return masked;
66
+ }
67
+ /** Remove masks of dropped peers from the aggregated sum. */
68
+ unmask(maskedSum, droppedMasks) {
69
+ const unmasked = {};
70
+ for (const [key, arr] of Object.entries(maskedSum)) {
71
+ if (!arr)
72
+ continue;
73
+ const result = new Float32Array(arr);
74
+ const mask = droppedMasks.get(key);
75
+ if (mask) {
76
+ for (let i = 0; i < result.length; i++) {
77
+ result[i] = result[i] - (mask[i] ?? 0);
78
+ }
79
+ }
80
+ unmasked[key] = result;
81
+ }
82
+ return unmasked;
83
+ }
84
+ }
85
+ // ---------------------------------------------------------------------------
86
+ // Shamir's Secret Sharing (for SecAgg+)
87
+ // ---------------------------------------------------------------------------
88
+ // Operates in GF(2^31 - 1) — a Mersenne prime field
89
+ const PRIME = 2147483647;
90
+ function modPow(base, exp, mod) {
91
+ let result = 1;
92
+ base = base % mod;
93
+ while (exp > 0) {
94
+ if (exp % 2 === 1) {
95
+ result = Number((BigInt(result) * BigInt(base)) % BigInt(mod));
96
+ }
97
+ exp = Math.floor(exp / 2);
98
+ base = Number((BigInt(base) * BigInt(base)) % BigInt(mod));
99
+ }
100
+ return result;
101
+ }
102
+ function modInverse(a, mod) {
103
+ return modPow(a, mod - 2, mod);
104
+ }
105
+ /**
106
+ * Split a secret into `numShares` shares requiring `threshold` to reconstruct.
107
+ */
108
+ export function shamirSplit(secret, threshold, numShares) {
109
+ // Generate random coefficients for polynomial
110
+ const coeffs = [secret % PRIME];
111
+ for (let i = 1; i < threshold; i++) {
112
+ const randomBytes = new Uint32Array(1);
113
+ crypto.getRandomValues(randomBytes);
114
+ coeffs.push(randomBytes[0] % PRIME);
115
+ }
116
+ const shares = [];
117
+ for (let x = 1; x <= numShares; x++) {
118
+ let y = 0;
119
+ for (let i = 0; i < coeffs.length; i++) {
120
+ y = Number((BigInt(y) + BigInt(coeffs[i]) * BigInt(modPow(x, i, PRIME))) %
121
+ BigInt(PRIME));
122
+ }
123
+ shares.push({ x, y });
124
+ }
125
+ return shares;
126
+ }
127
+ /**
128
+ * Reconstruct a secret from `threshold` shares via Lagrange interpolation.
129
+ */
130
+ export function shamirReconstruct(shares) {
131
+ let secret = 0;
132
+ const n = shares.length;
133
+ for (let i = 0; i < n; i++) {
134
+ let num = 1;
135
+ let den = 1;
136
+ for (let j = 0; j < n; j++) {
137
+ if (i === j)
138
+ continue;
139
+ num = Number((BigInt(num) * BigInt(PRIME - shares[j].x)) % BigInt(PRIME));
140
+ den = Number((BigInt(den) *
141
+ BigInt((shares[i].x - shares[j].x + PRIME) % PRIME)) %
142
+ BigInt(PRIME));
143
+ }
144
+ const lagrange = Number((BigInt(num) * BigInt(modInverse(den, PRIME))) % BigInt(PRIME));
145
+ secret = Number((BigInt(secret) + BigInt(shares[i].y) * BigInt(lagrange)) %
146
+ BigInt(PRIME));
147
+ }
148
+ return secret;
149
+ }
150
+ // ---------------------------------------------------------------------------
151
+ // SecAggPlus
152
+ // ---------------------------------------------------------------------------
153
+ export class SecAggPlus extends SecureAggregation {
154
+ threshold;
155
+ constructor(threshold) {
156
+ super();
157
+ this.threshold = threshold;
158
+ }
159
+ /**
160
+ * Split a shared secret into Shamir shares so that any `threshold`
161
+ * surviving peers can reconstruct the mask of a dropped peer.
162
+ */
163
+ splitSecret(secret, numPeers) {
164
+ return shamirSplit(secret, this.threshold, numPeers);
165
+ }
166
+ /** Reconstruct a dropped peer's secret from collected shares. */
167
+ reconstructSecret(shares) {
168
+ if (shares.length < this.threshold) {
169
+ throw new Error(`Need at least ${this.threshold} shares, got ${shares.length}.`);
170
+ }
171
+ return shamirReconstruct(shares.slice(0, this.threshold));
172
+ }
173
+ }
174
+ //# sourceMappingURL=secure-aggregation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secure-aggregation.js","sourceRoot":"","sources":["../src/secure-aggregation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @octomil/browser — Streaming inference engine
3
+ *
4
+ * Wraps server-sent events / streaming HTTP responses for incremental
5
+ * inference results (text generation, audio, video, image tiles).
6
+ */
7
+ import type { StreamingOptions, StreamingChunk, StreamingResult, TelemetryEvent } from "./types.js";
8
+ export declare class StreamingInferenceEngine {
9
+ private readonly serverUrl;
10
+ private readonly apiKey?;
11
+ private readonly onTelemetry?;
12
+ constructor(options: {
13
+ serverUrl: string;
14
+ apiKey?: string;
15
+ onTelemetry?: (event: TelemetryEvent) => void;
16
+ });
17
+ /**
18
+ * Stream inference results from the server.
19
+ *
20
+ * Returns an async iterable of chunks. Supports cancellation via AbortSignal.
21
+ */
22
+ stream(modelId: string, input: Record<string, unknown>, options?: StreamingOptions): AsyncGenerator<StreamingChunk, StreamingResult, undefined>;
23
+ private combineSignals;
24
+ }
25
+ //# sourceMappingURL=streaming.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming.d.ts","sourceRoot":"","sources":["../src/streaming.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,cAAc,EACf,MAAM,YAAY,CAAC;AAOpB,qBAAa,wBAAwB;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAkC;gBAEnD,OAAO,EAAE;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;KAC/C;IAMD;;;;OAIG;IACI,MAAM,CACX,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,GAAE,gBAAqB,GAC7B,cAAc,CAAC,cAAc,EAAE,eAAe,EAAE,SAAS,CAAC;IAmI7D,OAAO,CAAC,cAAc;CAavB"}