@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/README.md +191 -0
- package/dist/index.d.ts +457 -5
- package/dist/index.js +790 -9
- package/dist/index.js.map +1 -1
- package/package.json +9 -2
- package/src/core/interfaces/IWebSocketClient.ts +2 -2
- package/src/core/ws.ts +14 -6
- package/src/index.ts +66 -0
- package/src/pubsub/client.ts +3 -3
- package/src/pubsub/types.ts +1 -1
- package/src/vault/auth.ts +98 -0
- package/src/vault/client.ts +197 -0
- package/src/vault/crypto/aes.ts +271 -0
- package/src/vault/crypto/hkdf.ts +42 -0
- package/src/vault/crypto/index.ts +27 -0
- package/src/vault/crypto/shamir.ts +173 -0
- package/src/vault/index.ts +65 -0
- package/src/vault/quorum.ts +16 -0
- package/src/vault/transport/fanout.ts +94 -0
- package/src/vault/transport/guardian.ts +285 -0
- package/src/vault/transport/index.ts +19 -0
- package/src/vault/transport/types.ts +101 -0
- package/src/vault/types.ts +62 -0
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
|
|
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
|
-
|
|
927
|
-
|
|
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",
|
|
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
|
-
|
|
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
|