@debros/network-ts-sdk 0.6.1 → 0.7.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/dist/index.js CHANGED
@@ -909,7 +909,11 @@ var WSClient = class {
909
909
  this.ws.addEventListener("error", (event) => {
910
910
  console.error("[WSClient] WebSocket error:", event);
911
911
  clearTimeout(timeout);
912
- const error = new SDKError("WebSocket error", 500, "WS_ERROR", event);
912
+ const details = { type: event.type };
913
+ if ("message" in event) {
914
+ details.message = event.message;
915
+ }
916
+ const error = new SDKError("WebSocket error", 0, "WS_ERROR", details);
913
917
  if (this.onNetworkError) {
914
918
  this.onNetworkError(error, {
915
919
  method: "WS",
@@ -921,10 +925,13 @@ var WSClient = class {
921
925
  this.errorHandlers.forEach((handler) => handler(error));
922
926
  reject(error);
923
927
  });
924
- this.ws.addEventListener("close", () => {
928
+ this.ws.addEventListener("close", (event) => {
925
929
  clearTimeout(timeout);
926
- console.log("[WSClient] Connection closed");
927
- this.closeHandlers.forEach((handler) => handler());
930
+ const closeEvent = event;
931
+ const code = closeEvent.code ?? 1006;
932
+ const reason = closeEvent.reason ?? "";
933
+ console.log(`[WSClient] Connection closed (code: ${code}, reason: ${reason || "none"})`);
934
+ this.closeHandlers.forEach((handler) => handler(code, reason));
928
935
  });
929
936
  } catch (error) {
930
937
  reject(error);
@@ -995,7 +1002,7 @@ var WSClient = class {
995
1002
  */
996
1003
  send(data) {
997
1004
  if (this.ws?.readyState !== WebSocket.OPEN) {
998
- throw new SDKError("WebSocket is not connected", 500, "WS_NOT_CONNECTED");
1005
+ throw new SDKError("WebSocket is not connected", 0, "WS_NOT_CONNECTED");
999
1006
  }
1000
1007
  this.ws.send(data);
1001
1008
  }
@@ -1215,8 +1222,8 @@ var Subscription = class {
1215
1222
  this.errorHandlers.forEach((handler) => handler(error));
1216
1223
  };
1217
1224
  this.wsClient.onError(this.wsErrorHandler);
1218
- this.wsCloseHandler = () => {
1219
- this.closeHandlers.forEach((handler) => handler());
1225
+ this.wsCloseHandler = (code, reason) => {
1226
+ this.closeHandlers.forEach((handler) => handler(code, reason));
1220
1227
  };
1221
1228
  this.wsClient.onClose(this.wsCloseHandler);
1222
1229
  }
@@ -1689,6 +1696,746 @@ var FunctionsClient = class {
1689
1696
  }
1690
1697
  };
1691
1698
 
1699
+ // src/vault/transport/guardian.ts
1700
+ var GuardianError = class extends Error {
1701
+ constructor(code, message) {
1702
+ super(message);
1703
+ this.code = code;
1704
+ this.name = "GuardianError";
1705
+ }
1706
+ };
1707
+ var DEFAULT_TIMEOUT_MS = 1e4;
1708
+ var GuardianClient = class {
1709
+ constructor(endpoint, timeoutMs = DEFAULT_TIMEOUT_MS) {
1710
+ this.sessionToken = null;
1711
+ this.baseUrl = `http://${endpoint.address}:${endpoint.port}`;
1712
+ this.timeoutMs = timeoutMs;
1713
+ }
1714
+ /** Set a session token for authenticated V2 requests. */
1715
+ setSessionToken(token) {
1716
+ this.sessionToken = token;
1717
+ }
1718
+ /** Get the current session token. */
1719
+ getSessionToken() {
1720
+ return this.sessionToken;
1721
+ }
1722
+ /** Clear the session token. */
1723
+ clearSessionToken() {
1724
+ this.sessionToken = null;
1725
+ }
1726
+ // ── V1 endpoints ────────────────────────────────────────────────────
1727
+ /** GET /v1/vault/health */
1728
+ async health() {
1729
+ return this.get("/v1/vault/health");
1730
+ }
1731
+ /** GET /v1/vault/status */
1732
+ async status() {
1733
+ return this.get("/v1/vault/status");
1734
+ }
1735
+ /** GET /v1/vault/guardians */
1736
+ async guardians() {
1737
+ return this.get("/v1/vault/guardians");
1738
+ }
1739
+ /** POST /v1/vault/push — store a share (V1). */
1740
+ async push(identity, share) {
1741
+ return this.post("/v1/vault/push", {
1742
+ identity,
1743
+ share: uint8ToBase64(share)
1744
+ });
1745
+ }
1746
+ /** POST /v1/vault/pull — retrieve a share (V1). */
1747
+ async pull(identity) {
1748
+ const resp = await this.post("/v1/vault/pull", { identity });
1749
+ return base64ToUint8(resp.share);
1750
+ }
1751
+ /** Check if this guardian is reachable. */
1752
+ async isReachable() {
1753
+ try {
1754
+ await this.health();
1755
+ return true;
1756
+ } catch {
1757
+ return false;
1758
+ }
1759
+ }
1760
+ // ── V2 auth endpoints ───────────────────────────────────────────────
1761
+ /** POST /v2/vault/auth/challenge — request an auth challenge. */
1762
+ async requestChallenge(identity) {
1763
+ return this.post("/v2/vault/auth/challenge", { identity });
1764
+ }
1765
+ /** POST /v2/vault/auth/session — exchange challenge for session token. */
1766
+ async createSession(identity, nonce, created_ns, tag) {
1767
+ return this.post("/v2/vault/auth/session", {
1768
+ identity,
1769
+ nonce,
1770
+ created_ns,
1771
+ tag
1772
+ });
1773
+ }
1774
+ // ── V2 secrets CRUD ─────────────────────────────────────────────────
1775
+ /** PUT /v2/vault/secrets/{name} — store a secret. Requires session token. */
1776
+ async putSecret(name, share, version) {
1777
+ return this.authedRequest("PUT", `/v2/vault/secrets/${encodeURIComponent(name)}`, {
1778
+ share: uint8ToBase64(share),
1779
+ version
1780
+ });
1781
+ }
1782
+ /** GET /v2/vault/secrets/{name} — retrieve a secret. Requires session token. */
1783
+ async getSecret(name) {
1784
+ const resp = await this.authedRequest("GET", `/v2/vault/secrets/${encodeURIComponent(name)}`);
1785
+ return {
1786
+ share: base64ToUint8(resp.share),
1787
+ name: resp.name,
1788
+ version: resp.version,
1789
+ created_ns: resp.created_ns,
1790
+ updated_ns: resp.updated_ns
1791
+ };
1792
+ }
1793
+ /** DELETE /v2/vault/secrets/{name} — delete a secret. Requires session token. */
1794
+ async deleteSecret(name) {
1795
+ return this.authedRequest("DELETE", `/v2/vault/secrets/${encodeURIComponent(name)}`);
1796
+ }
1797
+ /** GET /v2/vault/secrets — list all secrets. Requires session token. */
1798
+ async listSecrets() {
1799
+ return this.authedRequest("GET", "/v2/vault/secrets");
1800
+ }
1801
+ // ── Internal HTTP methods ───────────────────────────────────────────
1802
+ async authedRequest(method, path, body) {
1803
+ if (!this.sessionToken) {
1804
+ throw new GuardianError("AUTH", "No session token set. Call authenticate() first.");
1805
+ }
1806
+ const controller = new AbortController();
1807
+ const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
1808
+ try {
1809
+ const headers = {
1810
+ "X-Session-Token": this.sessionToken
1811
+ };
1812
+ const init = {
1813
+ method,
1814
+ headers,
1815
+ signal: controller.signal
1816
+ };
1817
+ if (body !== void 0) {
1818
+ headers["Content-Type"] = "application/json";
1819
+ init.body = JSON.stringify(body);
1820
+ }
1821
+ const resp = await fetch(`${this.baseUrl}${path}`, init);
1822
+ if (!resp.ok) {
1823
+ const errBody = await resp.json().catch(() => ({}));
1824
+ const msg = errBody.error || `HTTP ${resp.status}`;
1825
+ throw new GuardianError(classifyHttpStatus(resp.status), msg);
1826
+ }
1827
+ return await resp.json();
1828
+ } catch (err) {
1829
+ throw classifyError(err);
1830
+ } finally {
1831
+ clearTimeout(timeout);
1832
+ }
1833
+ }
1834
+ async get(path) {
1835
+ const controller = new AbortController();
1836
+ const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
1837
+ try {
1838
+ const resp = await fetch(`${this.baseUrl}${path}`, {
1839
+ method: "GET",
1840
+ signal: controller.signal
1841
+ });
1842
+ if (!resp.ok) {
1843
+ const body = await resp.json().catch(() => ({}));
1844
+ const msg = body.error || `HTTP ${resp.status}`;
1845
+ throw new GuardianError(classifyHttpStatus(resp.status), msg);
1846
+ }
1847
+ return await resp.json();
1848
+ } catch (err) {
1849
+ throw classifyError(err);
1850
+ } finally {
1851
+ clearTimeout(timeout);
1852
+ }
1853
+ }
1854
+ async post(path, body) {
1855
+ const controller = new AbortController();
1856
+ const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
1857
+ try {
1858
+ const resp = await fetch(`${this.baseUrl}${path}`, {
1859
+ method: "POST",
1860
+ headers: { "Content-Type": "application/json" },
1861
+ body: JSON.stringify(body),
1862
+ signal: controller.signal
1863
+ });
1864
+ if (!resp.ok) {
1865
+ const errBody = await resp.json().catch(() => ({}));
1866
+ const msg = errBody.error || `HTTP ${resp.status}`;
1867
+ throw new GuardianError(classifyHttpStatus(resp.status), msg);
1868
+ }
1869
+ return await resp.json();
1870
+ } catch (err) {
1871
+ throw classifyError(err);
1872
+ } finally {
1873
+ clearTimeout(timeout);
1874
+ }
1875
+ }
1876
+ };
1877
+ function classifyHttpStatus(status) {
1878
+ if (status === 404) return "NOT_FOUND";
1879
+ if (status === 401 || status === 403) return "AUTH";
1880
+ if (status === 409) return "CONFLICT";
1881
+ if (status >= 500) return "SERVER_ERROR";
1882
+ return "NETWORK";
1883
+ }
1884
+ function classifyError(err) {
1885
+ if (err instanceof GuardianError) return err;
1886
+ if (err instanceof Error) {
1887
+ if (err.name === "AbortError") {
1888
+ return new GuardianError("TIMEOUT", `Request timed out: ${err.message}`);
1889
+ }
1890
+ if (err.name === "TypeError" || err.message.includes("fetch")) {
1891
+ return new GuardianError("NETWORK", `Network error: ${err.message}`);
1892
+ }
1893
+ return new GuardianError("NETWORK", err.message);
1894
+ }
1895
+ return new GuardianError("NETWORK", String(err));
1896
+ }
1897
+ function uint8ToBase64(bytes) {
1898
+ if (typeof Buffer !== "undefined") {
1899
+ return Buffer.from(bytes).toString("base64");
1900
+ }
1901
+ let binary = "";
1902
+ for (let i = 0; i < bytes.length; i++) {
1903
+ binary += String.fromCharCode(bytes[i]);
1904
+ }
1905
+ return btoa(binary);
1906
+ }
1907
+ function base64ToUint8(b64) {
1908
+ if (typeof Buffer !== "undefined") {
1909
+ return new Uint8Array(Buffer.from(b64, "base64"));
1910
+ }
1911
+ const binary = atob(b64);
1912
+ const bytes = new Uint8Array(binary.length);
1913
+ for (let i = 0; i < binary.length; i++) {
1914
+ bytes[i] = binary.charCodeAt(i);
1915
+ }
1916
+ return bytes;
1917
+ }
1918
+
1919
+ // src/vault/auth.ts
1920
+ var AuthClient2 = class {
1921
+ constructor(identityHex, timeoutMs = 1e4) {
1922
+ this.sessions = /* @__PURE__ */ new Map();
1923
+ this.identityHex = identityHex;
1924
+ this.timeoutMs = timeoutMs;
1925
+ }
1926
+ /**
1927
+ * Authenticate with a guardian and cache the session token.
1928
+ * Returns a GuardianClient with the session token set.
1929
+ */
1930
+ async authenticate(endpoint) {
1931
+ const key = `${endpoint.address}:${endpoint.port}`;
1932
+ const cached = this.sessions.get(key);
1933
+ if (cached) {
1934
+ const nowNs = Date.now() * 1e6;
1935
+ if (cached.expiryNs > nowNs + 3e10) {
1936
+ const client2 = new GuardianClient(endpoint, this.timeoutMs);
1937
+ client2.setSessionToken(cached.token);
1938
+ return client2;
1939
+ }
1940
+ this.sessions.delete(key);
1941
+ }
1942
+ const client = new GuardianClient(endpoint, this.timeoutMs);
1943
+ const challenge = await client.requestChallenge(this.identityHex);
1944
+ const session = await client.createSession(
1945
+ this.identityHex,
1946
+ challenge.nonce,
1947
+ challenge.created_ns,
1948
+ challenge.tag
1949
+ );
1950
+ const token = `${session.identity}:${session.expiry_ns}:${session.tag}`;
1951
+ client.setSessionToken(token);
1952
+ this.sessions.set(key, { token, expiryNs: session.expiry_ns });
1953
+ return client;
1954
+ }
1955
+ /**
1956
+ * Authenticate with multiple guardians in parallel.
1957
+ * Returns authenticated GuardianClients for all that succeed.
1958
+ */
1959
+ async authenticateAll(endpoints) {
1960
+ const results = await Promise.allSettled(
1961
+ endpoints.map(async (ep) => {
1962
+ const client = await this.authenticate(ep);
1963
+ return { client, endpoint: ep };
1964
+ })
1965
+ );
1966
+ const authenticated = [];
1967
+ for (const r of results) {
1968
+ if (r.status === "fulfilled") {
1969
+ authenticated.push(r.value);
1970
+ }
1971
+ }
1972
+ return authenticated;
1973
+ }
1974
+ /** Clear all cached sessions. */
1975
+ clearSessions() {
1976
+ this.sessions.clear();
1977
+ }
1978
+ /** Get the identity hex string. */
1979
+ getIdentityHex() {
1980
+ return this.identityHex;
1981
+ }
1982
+ };
1983
+
1984
+ // src/vault/transport/fanout.ts
1985
+ async function fanOut(guardians, operation) {
1986
+ const results = await Promise.allSettled(
1987
+ guardians.map(async (endpoint) => {
1988
+ const client = new GuardianClient(endpoint);
1989
+ const result = await operation(client);
1990
+ return { endpoint, result, error: null };
1991
+ })
1992
+ );
1993
+ return results.map((r, i) => {
1994
+ if (r.status === "fulfilled") return r.value;
1995
+ const reason = r.reason;
1996
+ const errorCode = reason instanceof GuardianError ? reason.code : void 0;
1997
+ return {
1998
+ endpoint: guardians[i],
1999
+ result: null,
2000
+ error: reason.message,
2001
+ errorCode
2002
+ };
2003
+ });
2004
+ }
2005
+ async function fanOutIndexed(guardians, operation) {
2006
+ const results = await Promise.allSettled(
2007
+ guardians.map(async (endpoint, i) => {
2008
+ const client = new GuardianClient(endpoint);
2009
+ const result = await operation(client, i);
2010
+ return { endpoint, result, error: null };
2011
+ })
2012
+ );
2013
+ return results.map((r, i) => {
2014
+ if (r.status === "fulfilled") return r.value;
2015
+ const reason = r.reason;
2016
+ const errorCode = reason instanceof GuardianError ? reason.code : void 0;
2017
+ return {
2018
+ endpoint: guardians[i],
2019
+ result: null,
2020
+ error: reason.message,
2021
+ errorCode
2022
+ };
2023
+ });
2024
+ }
2025
+ function withTimeout(promise, ms) {
2026
+ return Promise.race([
2027
+ promise,
2028
+ new Promise(
2029
+ (_, reject) => setTimeout(() => reject(new Error(`timeout after ${ms}ms`)), ms)
2030
+ )
2031
+ ]);
2032
+ }
2033
+ async function withRetry(fn, attempts = 3) {
2034
+ let lastError;
2035
+ for (let i = 0; i < attempts; i++) {
2036
+ try {
2037
+ return await fn();
2038
+ } catch (err) {
2039
+ lastError = err;
2040
+ if (err instanceof GuardianError && (err.code === "AUTH" || err.code === "NOT_FOUND")) {
2041
+ throw err;
2042
+ }
2043
+ if (i < attempts - 1) {
2044
+ await new Promise((r) => setTimeout(r, 200 * Math.pow(2, i)));
2045
+ }
2046
+ }
2047
+ }
2048
+ throw lastError;
2049
+ }
2050
+
2051
+ // src/vault/crypto/shamir.ts
2052
+ import { randomBytes } from "@noble/ciphers/webcrypto";
2053
+ var IRREDUCIBLE = 283;
2054
+ var EXP_TABLE = new Uint8Array(512);
2055
+ var LOG_TABLE = new Uint8Array(256);
2056
+ (function buildTables() {
2057
+ let x = 1;
2058
+ for (let i = 0; i < 255; i++) {
2059
+ EXP_TABLE[i] = x;
2060
+ LOG_TABLE[x] = i;
2061
+ x = x ^ x << 1;
2062
+ if (x >= 256) x ^= IRREDUCIBLE;
2063
+ }
2064
+ for (let i = 255; i < 512; i++) {
2065
+ EXP_TABLE[i] = EXP_TABLE[i - 255];
2066
+ }
2067
+ })();
2068
+ function gfAdd(a, b) {
2069
+ return a ^ b;
2070
+ }
2071
+ function gfMul(a, b) {
2072
+ if (a === 0 || b === 0) return 0;
2073
+ return EXP_TABLE[LOG_TABLE[a] + LOG_TABLE[b]];
2074
+ }
2075
+ function gfDiv(a, b) {
2076
+ if (b === 0) throw new Error("GF(2^8): division by zero");
2077
+ if (a === 0) return 0;
2078
+ return EXP_TABLE[(LOG_TABLE[a] - LOG_TABLE[b] + 255) % 255];
2079
+ }
2080
+ function split(secret, n, k) {
2081
+ if (k < 2) throw new Error("Threshold K must be at least 2");
2082
+ if (n < k) throw new Error("Share count N must be >= threshold K");
2083
+ if (n > 255) throw new Error("Maximum 255 shares (GF(2^8) limit)");
2084
+ if (secret.length === 0) throw new Error("Secret must not be empty");
2085
+ const coefficients = new Array(secret.length);
2086
+ for (let i = 0; i < secret.length; i++) {
2087
+ const poly = new Uint8Array(k);
2088
+ poly[0] = secret[i];
2089
+ const rand = randomBytes(k - 1);
2090
+ poly.set(rand, 1);
2091
+ coefficients[i] = poly;
2092
+ }
2093
+ const shares = [];
2094
+ for (let xi = 1; xi <= n; xi++) {
2095
+ const y = new Uint8Array(secret.length);
2096
+ for (let byteIdx = 0; byteIdx < secret.length; byteIdx++) {
2097
+ y[byteIdx] = evaluatePolynomial(coefficients[byteIdx], xi);
2098
+ }
2099
+ shares.push({ x: xi, y });
2100
+ }
2101
+ for (const poly of coefficients) {
2102
+ poly.fill(0);
2103
+ }
2104
+ return shares;
2105
+ }
2106
+ function evaluatePolynomial(coeffs, x) {
2107
+ let result = 0;
2108
+ for (let i = coeffs.length - 1; i >= 0; i--) {
2109
+ result = gfAdd(gfMul(result, x), coeffs[i]);
2110
+ }
2111
+ return result;
2112
+ }
2113
+ function combine(shares) {
2114
+ if (shares.length < 2) throw new Error("Need at least 2 shares");
2115
+ const secretLength = shares[0].y.length;
2116
+ for (const share of shares) {
2117
+ if (share.y.length !== secretLength) {
2118
+ throw new Error("All shares must have the same data length");
2119
+ }
2120
+ if (share.x === 0) {
2121
+ throw new Error("Share index must not be 0");
2122
+ }
2123
+ }
2124
+ const xValues = new Set(shares.map((s) => s.x));
2125
+ if (xValues.size !== shares.length) {
2126
+ throw new Error("Duplicate share indices");
2127
+ }
2128
+ const secret = new Uint8Array(secretLength);
2129
+ for (let byteIdx = 0; byteIdx < secretLength; byteIdx++) {
2130
+ let value = 0;
2131
+ for (let i = 0; i < shares.length; i++) {
2132
+ const xi = shares[i].x;
2133
+ const yi = shares[i].y[byteIdx];
2134
+ let basis = 1;
2135
+ for (let j = 0; j < shares.length; j++) {
2136
+ if (i === j) continue;
2137
+ const xj = shares[j].x;
2138
+ basis = gfMul(basis, gfDiv(xj, gfAdd(xi, xj)));
2139
+ }
2140
+ value = gfAdd(value, gfMul(yi, basis));
2141
+ }
2142
+ secret[byteIdx] = value;
2143
+ }
2144
+ return secret;
2145
+ }
2146
+
2147
+ // src/vault/quorum.ts
2148
+ function adaptiveThreshold(n) {
2149
+ return Math.max(3, Math.floor(n / 3));
2150
+ }
2151
+ function writeQuorum(n) {
2152
+ if (n === 0) return 0;
2153
+ if (n <= 2) return n;
2154
+ return Math.ceil(2 * n / 3);
2155
+ }
2156
+
2157
+ // src/vault/client.ts
2158
+ var PULL_TIMEOUT_MS = 1e4;
2159
+ var VaultClient = class {
2160
+ constructor(config) {
2161
+ this.config = config;
2162
+ this.auth = new AuthClient2(config.identityHex, config.timeoutMs);
2163
+ }
2164
+ /**
2165
+ * Store a secret across guardian nodes using Shamir splitting.
2166
+ *
2167
+ * @param name - Secret name (alphanumeric, _, -, max 128 chars)
2168
+ * @param data - Secret data to store
2169
+ * @param version - Monotonic version number (must be > previous)
2170
+ */
2171
+ async store(name, data, version) {
2172
+ const guardians = this.config.guardians;
2173
+ const n = guardians.length;
2174
+ const k = adaptiveThreshold(n);
2175
+ const shares = split(data, n, k);
2176
+ const authed = await this.auth.authenticateAll(guardians);
2177
+ const results = await Promise.allSettled(
2178
+ authed.map(async ({ client, endpoint }, _i) => {
2179
+ const guardianIdx = guardians.indexOf(endpoint);
2180
+ const share = shares[guardianIdx];
2181
+ if (!share) throw new Error("share index out of bounds");
2182
+ const shareBytes = new Uint8Array(1 + share.y.length);
2183
+ shareBytes[0] = share.x;
2184
+ shareBytes.set(share.y, 1);
2185
+ return withRetry(() => client.putSecret(name, shareBytes, version));
2186
+ })
2187
+ );
2188
+ for (const share of shares) {
2189
+ share.y.fill(0);
2190
+ }
2191
+ const guardianResults = authed.map(({ endpoint }, i) => {
2192
+ const ep = `${endpoint.address}:${endpoint.port}`;
2193
+ const r = results[i];
2194
+ if (r.status === "fulfilled") {
2195
+ return { endpoint: ep, success: true };
2196
+ }
2197
+ return { endpoint: ep, success: false, error: r.reason.message };
2198
+ });
2199
+ const ackCount = results.filter((r) => r.status === "fulfilled").length;
2200
+ const failCount = results.filter((r) => r.status === "rejected").length;
2201
+ const w = writeQuorum(n);
2202
+ return {
2203
+ ackCount,
2204
+ totalContacted: authed.length,
2205
+ failCount,
2206
+ quorumMet: ackCount >= w,
2207
+ guardianResults
2208
+ };
2209
+ }
2210
+ /**
2211
+ * Retrieve and reconstruct a secret from guardian nodes.
2212
+ *
2213
+ * @param name - Secret name
2214
+ */
2215
+ async retrieve(name) {
2216
+ const guardians = this.config.guardians;
2217
+ const n = guardians.length;
2218
+ const k = adaptiveThreshold(n);
2219
+ const authed = await this.auth.authenticateAll(guardians);
2220
+ const pullResults = await Promise.allSettled(
2221
+ authed.map(async ({ client }) => {
2222
+ const resp = await withTimeout(client.getSecret(name), PULL_TIMEOUT_MS);
2223
+ const shareBytes = resp.share;
2224
+ if (shareBytes.length < 2) throw new Error("Share too short");
2225
+ return {
2226
+ x: shareBytes[0],
2227
+ y: shareBytes.slice(1)
2228
+ };
2229
+ })
2230
+ );
2231
+ const shares = [];
2232
+ for (const r of pullResults) {
2233
+ if (r.status === "fulfilled") {
2234
+ shares.push(r.value);
2235
+ }
2236
+ }
2237
+ if (shares.length < k) {
2238
+ throw new Error(
2239
+ `Not enough shares: collected ${shares.length} of ${k} required (contacted ${authed.length} guardians)`
2240
+ );
2241
+ }
2242
+ const data = combine(shares);
2243
+ for (const share of shares) {
2244
+ share.y.fill(0);
2245
+ }
2246
+ return {
2247
+ data,
2248
+ sharesCollected: shares.length
2249
+ };
2250
+ }
2251
+ /**
2252
+ * List all secrets for this identity.
2253
+ * Queries the first reachable guardian (metadata is replicated).
2254
+ */
2255
+ async list() {
2256
+ const guardians = this.config.guardians;
2257
+ const authed = await this.auth.authenticateAll(guardians);
2258
+ if (authed.length === 0) {
2259
+ throw new Error("No guardians reachable");
2260
+ }
2261
+ const resp = await authed[0].client.listSecrets();
2262
+ return { secrets: resp.secrets };
2263
+ }
2264
+ /**
2265
+ * Delete a secret from all guardian nodes.
2266
+ *
2267
+ * @param name - Secret name to delete
2268
+ */
2269
+ async delete(name) {
2270
+ const guardians = this.config.guardians;
2271
+ const n = guardians.length;
2272
+ const authed = await this.auth.authenticateAll(guardians);
2273
+ const results = await Promise.allSettled(
2274
+ authed.map(async ({ client }) => {
2275
+ return withRetry(() => client.deleteSecret(name));
2276
+ })
2277
+ );
2278
+ const ackCount = results.filter((r) => r.status === "fulfilled").length;
2279
+ const w = writeQuorum(n);
2280
+ return {
2281
+ ackCount,
2282
+ totalContacted: authed.length,
2283
+ quorumMet: ackCount >= w
2284
+ };
2285
+ }
2286
+ /** Clear all cached auth sessions. */
2287
+ clearSessions() {
2288
+ this.auth.clearSessions();
2289
+ }
2290
+ };
2291
+
2292
+ // src/vault/crypto/aes.ts
2293
+ import { gcm } from "@noble/ciphers/aes";
2294
+ import { randomBytes as randomBytes2 } from "@noble/ciphers/webcrypto";
2295
+ import { bytesToHex, hexToBytes, concatBytes } from "@noble/hashes/utils";
2296
+ var KEY_SIZE = 32;
2297
+ var NONCE_SIZE = 12;
2298
+ var TAG_SIZE = 16;
2299
+ function encrypt(plaintext, key, aad) {
2300
+ validateKey(key);
2301
+ const nonce = randomBytes2(NONCE_SIZE);
2302
+ const cipher = gcm(key, nonce, aad);
2303
+ const ciphertext = cipher.encrypt(plaintext);
2304
+ return {
2305
+ ciphertext,
2306
+ nonce,
2307
+ aad
2308
+ };
2309
+ }
2310
+ function decrypt(encryptedData, key) {
2311
+ validateKey(key);
2312
+ validateNonce(encryptedData.nonce);
2313
+ const cipher = gcm(key, encryptedData.nonce, encryptedData.aad);
2314
+ try {
2315
+ return cipher.decrypt(encryptedData.ciphertext);
2316
+ } catch (error) {
2317
+ throw new Error("Decryption failed: invalid ciphertext or authentication tag");
2318
+ }
2319
+ }
2320
+ function encryptString(message, key, aad) {
2321
+ const plaintext = new TextEncoder().encode(message);
2322
+ try {
2323
+ return encrypt(plaintext, key, aad);
2324
+ } finally {
2325
+ plaintext.fill(0);
2326
+ }
2327
+ }
2328
+ function decryptString(encryptedData, key) {
2329
+ const plaintext = decrypt(encryptedData, key);
2330
+ try {
2331
+ return new TextDecoder().decode(plaintext);
2332
+ } finally {
2333
+ plaintext.fill(0);
2334
+ }
2335
+ }
2336
+ function serialize(encryptedData) {
2337
+ const data = concatBytes(encryptedData.nonce, encryptedData.ciphertext);
2338
+ return {
2339
+ data,
2340
+ aad: encryptedData.aad
2341
+ };
2342
+ }
2343
+ function deserialize(serialized) {
2344
+ if (serialized.data.length < NONCE_SIZE + TAG_SIZE) {
2345
+ throw new Error("Invalid serialized data: too short");
2346
+ }
2347
+ const nonce = serialized.data.slice(0, NONCE_SIZE);
2348
+ const ciphertext = serialized.data.slice(NONCE_SIZE);
2349
+ return {
2350
+ ciphertext,
2351
+ nonce,
2352
+ aad: serialized.aad
2353
+ };
2354
+ }
2355
+ function encryptAndSerialize(plaintext, key, aad) {
2356
+ const encrypted = encrypt(plaintext, key, aad);
2357
+ return serialize(encrypted);
2358
+ }
2359
+ function deserializeAndDecrypt(serialized, key) {
2360
+ const encrypted = deserialize(serialized);
2361
+ return decrypt(encrypted, key);
2362
+ }
2363
+ function toHex(encryptedData) {
2364
+ const serialized = serialize(encryptedData);
2365
+ return bytesToHex(serialized.data);
2366
+ }
2367
+ function fromHex(hex, aad) {
2368
+ const normalized = hex.startsWith("0x") ? hex.slice(2) : hex;
2369
+ const data = hexToBytes(normalized);
2370
+ return deserialize({ data, aad });
2371
+ }
2372
+ function toBase64(encryptedData) {
2373
+ const serialized = serialize(encryptedData);
2374
+ if (typeof btoa === "function") {
2375
+ return btoa(String.fromCharCode(...serialized.data));
2376
+ } else {
2377
+ return Buffer.from(serialized.data).toString("base64");
2378
+ }
2379
+ }
2380
+ function fromBase64(base64, aad) {
2381
+ let data;
2382
+ if (typeof atob === "function") {
2383
+ const binary = atob(base64);
2384
+ data = new Uint8Array(binary.length);
2385
+ for (let i = 0; i < binary.length; i++) {
2386
+ data[i] = binary.charCodeAt(i);
2387
+ }
2388
+ } else {
2389
+ data = new Uint8Array(Buffer.from(base64, "base64"));
2390
+ }
2391
+ return deserialize({ data, aad });
2392
+ }
2393
+ function validateKey(key) {
2394
+ if (!(key instanceof Uint8Array)) {
2395
+ throw new Error("Key must be a Uint8Array");
2396
+ }
2397
+ if (key.length !== KEY_SIZE) {
2398
+ throw new Error(`Invalid key length: expected ${KEY_SIZE}, got ${key.length}`);
2399
+ }
2400
+ }
2401
+ function validateNonce(nonce) {
2402
+ if (!(nonce instanceof Uint8Array)) {
2403
+ throw new Error("Nonce must be a Uint8Array");
2404
+ }
2405
+ if (nonce.length !== NONCE_SIZE) {
2406
+ throw new Error(`Invalid nonce length: expected ${NONCE_SIZE}, got ${nonce.length}`);
2407
+ }
2408
+ }
2409
+ function generateKey() {
2410
+ return randomBytes2(KEY_SIZE);
2411
+ }
2412
+ function generateNonce() {
2413
+ return randomBytes2(NONCE_SIZE);
2414
+ }
2415
+ function clearKey(key) {
2416
+ key.fill(0);
2417
+ }
2418
+ function isValidEncryptedData(data) {
2419
+ return data.nonce instanceof Uint8Array && data.nonce.length === NONCE_SIZE && data.ciphertext instanceof Uint8Array && data.ciphertext.length >= TAG_SIZE;
2420
+ }
2421
+
2422
+ // src/vault/crypto/hkdf.ts
2423
+ import { hkdf } from "@noble/hashes/hkdf";
2424
+ import { sha256 } from "@noble/hashes/sha256";
2425
+ var DEFAULT_KEY_LENGTH = 32;
2426
+ var MAX_KEY_LENGTH = 255 * 32;
2427
+ function deriveKeyHKDF(ikm, salt, info, length = DEFAULT_KEY_LENGTH) {
2428
+ if (!ikm || ikm.length === 0) {
2429
+ throw new Error("HKDF: input key material must not be empty");
2430
+ }
2431
+ if (length <= 0 || length > MAX_KEY_LENGTH) {
2432
+ throw new Error(`HKDF: output length must be between 1 and ${MAX_KEY_LENGTH}`);
2433
+ }
2434
+ const saltBytes = typeof salt === "string" ? new TextEncoder().encode(salt) : salt;
2435
+ const infoBytes = typeof info === "string" ? new TextEncoder().encode(info) : info;
2436
+ return hkdf(sha256, ikm, saltBytes, infoBytes, length);
2437
+ }
2438
+
1692
2439
  // src/index.ts
1693
2440
  function createClient(config) {
1694
2441
  const httpClient = new HttpClient({
@@ -1717,6 +2464,7 @@ function createClient(config) {
1717
2464
  const cache = new CacheClient(httpClient);
1718
2465
  const storage = new StorageClient(httpClient);
1719
2466
  const functions = new FunctionsClient(httpClient, config.functionsConfig);
2467
+ const vault = config.vaultConfig ? new VaultClient(config.vaultConfig) : null;
1720
2468
  return {
1721
2469
  auth,
1722
2470
  db,
@@ -1724,7 +2472,8 @@ function createClient(config) {
1724
2472
  network,
1725
2473
  cache,
1726
2474
  storage,
1727
- functions
2475
+ functions,
2476
+ vault
1728
2477
  };
1729
2478
  }
1730
2479
  export {
@@ -1732,9 +2481,13 @@ export {
1732
2481
  CacheClient,
1733
2482
  DBClient,
1734
2483
  FunctionsClient,
2484
+ GuardianClient,
2485
+ GuardianError,
1735
2486
  HttpClient,
2487
+ KEY_SIZE,
1736
2488
  LocalStorageAdapter,
1737
2489
  MemoryStorage,
2490
+ NONCE_SIZE,
1738
2491
  NetworkClient,
1739
2492
  PubSubClient,
1740
2493
  QueryBuilder,
@@ -1742,7 +2495,35 @@ export {
1742
2495
  SDKError,
1743
2496
  StorageClient,
1744
2497
  Subscription,
2498
+ TAG_SIZE,
2499
+ AuthClient2 as VaultAuthClient,
2500
+ VaultClient,
1745
2501
  WSClient,
1746
- createClient
2502
+ adaptiveThreshold,
2503
+ clearKey,
2504
+ createClient,
2505
+ decrypt,
2506
+ decryptString,
2507
+ deriveKeyHKDF,
2508
+ deserializeAndDecrypt,
2509
+ deserialize as deserializeEncrypted,
2510
+ encrypt,
2511
+ encryptAndSerialize,
2512
+ encryptString,
2513
+ fromBase64 as encryptedFromBase64,
2514
+ fromHex as encryptedFromHex,
2515
+ toBase64 as encryptedToBase64,
2516
+ toHex as encryptedToHex,
2517
+ fanOut,
2518
+ fanOutIndexed,
2519
+ generateKey,
2520
+ generateNonce,
2521
+ isValidEncryptedData,
2522
+ serialize as serializeEncrypted,
2523
+ combine as shamirCombine,
2524
+ split as shamirSplit,
2525
+ withRetry,
2526
+ withTimeout,
2527
+ writeQuorum
1747
2528
  };
1748
2529
  //# sourceMappingURL=index.js.map