@private.me/xbind 3.0.0 → 3.0.2
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 +55 -7
- package/dist-standalone/_deps/mldsa-wasm/dist/mldsa.js +1920 -1
- package/dist-standalone/_deps/shared/cjs/errors.js +729 -1
- package/dist-standalone/_deps/shared/cjs/index.js +463 -1
- package/dist-standalone/_deps/shared/cjs/types.js +315 -1
- package/dist-standalone/_deps/shared/errors.js +244 -1
- package/dist-standalone/_deps/shared/index.js +72 -1
- package/dist-standalone/_deps/shared/types.js +86 -1
- package/dist-standalone/_deps/ux-helpers/cjs/errors.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/index.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/pagination.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/progress.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/search.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/types.js +1 -1
- package/dist-standalone/_deps/ux-helpers/errors.js +1 -1
- package/dist-standalone/_deps/ux-helpers/index.js +1 -1
- package/dist-standalone/_deps/ux-helpers/pagination.js +1 -1
- package/dist-standalone/_deps/ux-helpers/progress.js +1 -1
- package/dist-standalone/_deps/ux-helpers/search.js +1 -1
- package/dist-standalone/_deps/xchange/auto-accept.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/auto-accept.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/errors.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/index.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/invite-client.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/lazy-init.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/trust-integration.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/xchange.js +1 -1
- package/dist-standalone/_deps/xchange/errors.js +1 -1
- package/dist-standalone/_deps/xchange/index.js +1 -1
- package/dist-standalone/_deps/xchange/invite-client.js +1 -1
- package/dist-standalone/_deps/xchange/lazy-init.js +1 -1
- package/dist-standalone/_deps/xchange/trust-integration.js +1 -1
- package/dist-standalone/_deps/xchange/xchange.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/discovery.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/errors.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/index.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/registry.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/schema.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/types.js +1 -1
- package/dist-standalone/_deps/xregistry/discovery.js +1 -1
- package/dist-standalone/_deps/xregistry/errors.js +1 -1
- package/dist-standalone/_deps/xregistry/index.js +1 -1
- package/dist-standalone/_deps/xregistry/registry.js +1 -1
- package/dist-standalone/_deps/xregistry/schema.js +1 -1
- package/dist-standalone/_deps/xregistry/types.js +1 -1
- package/dist-standalone/agent-call.js +659 -1
- package/dist-standalone/agent-sdk.js +328 -1
- package/dist-standalone/agent.js +1800 -1
- package/dist-standalone/approval.js +193 -1
- package/dist-standalone/async-iterators.js +382 -1
- package/dist-standalone/auth.js +219 -1
- package/dist-standalone/auto-accept.js +229 -1
- package/dist-standalone/backup-config.js +201 -1
- package/dist-standalone/backup.js +326 -1
- package/dist-standalone/batch-operations.js +388 -1
- package/dist-standalone/cancellation.js +477 -1
- package/dist-standalone/checkpoint.js +186 -1
- package/dist-standalone/circuit-breaker.js +468 -1
- package/dist-standalone/cjs/agent-call.js +701 -1
- package/dist-standalone/cjs/agent-sdk.js +332 -1
- package/dist-standalone/cjs/agent.js +1837 -1
- package/dist-standalone/cjs/approval.js +199 -1
- package/dist-standalone/cjs/async-iterators.js +392 -1
- package/dist-standalone/cjs/auth.js +225 -1
- package/dist-standalone/cjs/auto-accept.js +233 -1
- package/dist-standalone/cjs/backup-config.js +207 -1
- package/dist-standalone/cjs/backup.js +330 -1
- package/dist-standalone/cjs/batch-operations.js +397 -1
- package/dist-standalone/cjs/cancellation.js +490 -1
- package/dist-standalone/cjs/checkpoint.js +193 -1
- package/dist-standalone/cjs/circuit-breaker.js +476 -1
- package/dist-standalone/cjs/cli/init.js +492 -1
- package/dist-standalone/cjs/config-validation.js +522 -1
- package/dist-standalone/cjs/connect.js +312 -1
- package/dist-standalone/cjs/connection-pool.js +506 -1
- package/dist-standalone/cjs/correlation-id.js +339 -1
- package/dist-standalone/cjs/crypto-utils.js +176 -1
- package/dist-standalone/cjs/debug-mode.js +534 -1
- package/dist-standalone/cjs/did-document.js +101 -1
- package/dist-standalone/cjs/did-privateme.js +130 -1
- package/dist-standalone/cjs/did-web.js +201 -1
- package/dist-standalone/cjs/discovery.js +462 -1
- package/dist-standalone/cjs/dual-mode.js +251 -1
- package/dist-standalone/cjs/email-templates.js +313 -1
- package/dist-standalone/cjs/email-transport.js +239 -1
- package/dist-standalone/cjs/envelope.js +538 -1
- package/dist-standalone/cjs/errors.js +913 -1
- package/dist-standalone/cjs/event-emitter.js +461 -1
- package/dist-standalone/cjs/gateway-state.js +55 -1
- package/dist-standalone/cjs/gateway-transport.js +120 -1
- package/dist-standalone/cjs/graceful-degradation.js +403 -1
- package/dist-standalone/cjs/guardrails.js +223 -1
- package/dist-standalone/cjs/health-check.js +336 -1
- package/dist-standalone/cjs/http-compat.js +272 -1
- package/dist-standalone/cjs/http-status-map.js +571 -1
- package/dist-standalone/cjs/identity.js +645 -1
- package/dist-standalone/cjs/index.js +406 -1
- package/dist-standalone/cjs/invitation.js +421 -1
- package/dist-standalone/cjs/invite.js +328 -1
- package/dist-standalone/cjs/key-agreement.js +335 -1
- package/dist-standalone/cjs/lazy-init.js +300 -1
- package/dist-standalone/cjs/logger.js +291 -1
- package/dist-standalone/cjs/mdns-discovery.js +202 -1
- package/dist-standalone/cjs/nonce-store.js +80 -1
- package/dist-standalone/cjs/pairing-manager.js +223 -1
- package/dist-standalone/cjs/plugin-system.js +264 -1
- package/dist-standalone/cjs/plugins/logging.js +168 -1
- package/dist-standalone/cjs/plugins/metrics.js +181 -1
- package/dist-standalone/cjs/plugins/validation.js +302 -1
- package/dist-standalone/cjs/policy.js +320 -1
- package/dist-standalone/cjs/progress-callbacks.js +583 -1
- package/dist-standalone/cjs/redis-nonce-store.js +76 -1
- package/dist-standalone/cjs/registry-middleware.js +50 -1
- package/dist-standalone/cjs/retry-strategies.js +544 -1
- package/dist-standalone/cjs/retry-transport.js +102 -1
- package/dist-standalone/cjs/runtime/browser.js +533 -1
- package/dist-standalone/cjs/runtime/edge.js +526 -1
- package/dist-standalone/cjs/runtime/react-native.js +394 -1
- package/dist-standalone/cjs/security-policy.js +245 -1
- package/dist-standalone/cjs/serialization.js +1040 -1
- package/dist-standalone/cjs/split-channel.js +225 -1
- package/dist-standalone/cjs/subscription-proof.js +230 -1
- package/dist-standalone/cjs/succession.js +148 -1
- package/dist-standalone/cjs/timeouts.js +412 -1
- package/dist-standalone/cjs/trace-context.js +424 -1
- package/dist-standalone/cjs/trace-spans.js +495 -1
- package/dist-standalone/cjs/transport.js +63 -1
- package/dist-standalone/cjs/trust-registry.js +991 -1
- package/dist-standalone/cjs/types/error-response.js +56 -1
- package/dist-standalone/cjs/vault-auth.js +178 -1
- package/dist-standalone/cjs/vault-store-loader.js +194 -1
- package/dist-standalone/cjs/verify.js +25 -1
- package/dist-standalone/cjs/version-info.js +543 -1
- package/dist-standalone/cjs/xfetch.js +340 -1
- package/dist-standalone/cli/init.js +455 -1
- package/dist-standalone/cli/setup.js +514 -1
- package/dist-standalone/cli/types.js +27 -1
- package/dist-standalone/cli/xbind.js +148 -1
- package/dist-standalone/config-validation.js +513 -1
- package/dist-standalone/connect.js +274 -1
- package/dist-standalone/connection-pool.js +500 -1
- package/dist-standalone/correlation-id.js +326 -1
- package/dist-standalone/crypto-utils.js +157 -1
- package/dist-standalone/debug-mode.js +510 -1
- package/dist-standalone/did-document.js +96 -1
- package/dist-standalone/did-privateme.js +121 -1
- package/dist-standalone/did-web.js +196 -1
- package/dist-standalone/discovery.js +458 -1
- package/dist-standalone/dual-mode.js +247 -1
- package/dist-standalone/email-templates.js +309 -1
- package/dist-standalone/email-transport.js +232 -1
- package/dist-standalone/envelope.js +525 -1
- package/dist-standalone/errors.js +896 -1
- package/dist-standalone/event-emitter.js +456 -1
- package/dist-standalone/gateway-state.js +51 -1
- package/dist-standalone/gateway-transport.js +116 -1
- package/dist-standalone/graceful-degradation.js +396 -1
- package/dist-standalone/guardrails.js +216 -1
- package/dist-standalone/health-check.js +332 -1
- package/dist-standalone/http-compat.js +267 -1
- package/dist-standalone/http-status-map.js +561 -1
- package/dist-standalone/identity.js +619 -1
- package/dist-standalone/index.js +78 -1
- package/dist-standalone/invitation.js +415 -1
- package/dist-standalone/invite.js +324 -1
- package/dist-standalone/key-agreement.js +325 -1
- package/dist-standalone/lazy-init.js +295 -1
- package/dist-standalone/logger.js +285 -1
- package/dist-standalone/mdns-discovery.js +195 -1
- package/dist-standalone/nonce-store.js +76 -1
- package/dist-standalone/pairing-manager.js +219 -1
- package/dist-standalone/plugin-system.js +257 -1
- package/dist-standalone/plugins/logging.d.ts +84 -0
- package/dist-standalone/plugins/logging.js +163 -0
- package/dist-standalone/plugins/metrics.d.ts +111 -0
- package/dist-standalone/plugins/metrics.js +176 -0
- package/dist-standalone/plugins/validation.d.ts +104 -0
- package/dist-standalone/plugins/validation.js +297 -0
- package/dist-standalone/policy.js +315 -1
- package/dist-standalone/progress-callbacks.js +576 -1
- package/dist-standalone/redis-nonce-store.js +72 -1
- package/dist-standalone/registry-middleware.js +47 -1
- package/dist-standalone/retry-strategies.js +534 -1
- package/dist-standalone/retry-transport.js +98 -1
- package/dist-standalone/runtime/browser.d.ts +311 -0
- package/dist-standalone/runtime/browser.js +516 -0
- package/dist-standalone/runtime/edge.d.ts +282 -0
- package/dist-standalone/runtime/edge.js +511 -0
- package/dist-standalone/runtime/react-native.d.ts +157 -0
- package/dist-standalone/runtime/react-native.js +383 -0
- package/dist-standalone/security-policy.js +239 -1
- package/dist-standalone/serialization.js +1031 -1
- package/dist-standalone/split-channel.js +219 -1
- package/dist-standalone/subscription-proof.js +224 -1
- package/dist-standalone/succession.js +142 -1
- package/dist-standalone/timeouts.js +398 -1
- package/dist-standalone/trace-context.js +414 -1
- package/dist-standalone/trace-spans.js +488 -1
- package/dist-standalone/transport.js +59 -1
- package/dist-standalone/trust-registry.js +950 -1
- package/dist-standalone/types/error-response.d.ts +209 -0
- package/dist-standalone/types/error-response.js +52 -0
- package/dist-standalone/vault-auth.js +174 -1
- package/dist-standalone/vault-store-loader.js +187 -1
- package/dist-standalone/verify.js +16 -1
- package/dist-standalone/version-info.js +530 -1
- package/dist-standalone/xfetch.js +335 -1
- package/package.json +4 -10
- package/share1.dat +0 -0
- package/dist-standalone/_deps/mldsa-wasm/LICENSE +0 -24
- package/dist-standalone/_deps/mldsa-wasm/package.json +0 -46
- package/dist-standalone/_deps/shared/cjs/package.json +0 -1
- package/dist-standalone/_deps/ux-helpers/cjs/package.json +0 -1
- package/dist-standalone/_deps/xchange/cjs/package.json +0 -1
- package/dist-standalone/_deps/xregistry/cjs/package.json +0 -1
- package/dist-standalone/cjs/package.json +0 -3
- package/dist-standalone/package.json +0 -10
|
@@ -1 +1,225 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Xlock auth challenge module for Agent SDK.
|
|
4
|
+
*
|
|
5
|
+
* Provides requestAuth(), respondToChallenge(), and onChallenge()
|
|
6
|
+
* functions that work with an Agent instance and the XBind gateway.
|
|
7
|
+
*
|
|
8
|
+
* These functions are also re-exported as thin methods on the Agent class.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.requestAuth = requestAuth;
|
|
12
|
+
exports.respondToChallenge = respondToChallenge;
|
|
13
|
+
exports.onChallenge = onChallenge;
|
|
14
|
+
exports.generateRegistrationQR = generateRegistrationQR;
|
|
15
|
+
const shared_1 = require("../_deps/shared/index.js");
|
|
16
|
+
const envelope_js_1 = require("./envelope.js");
|
|
17
|
+
/** Default poll interval for challenge status. */
|
|
18
|
+
const DEFAULT_POLL_INTERVAL_MS = 2_000;
|
|
19
|
+
/** Default TTL for challenges (5 minutes). */
|
|
20
|
+
const DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
21
|
+
/** Max poll iterations (TTL / pollInterval + safety margin). */
|
|
22
|
+
const MAX_POLL_ITERATIONS = 200;
|
|
23
|
+
/**
|
|
24
|
+
* Create an auth challenge and poll until the user responds.
|
|
25
|
+
*
|
|
26
|
+
* Sends a challenge via the XBind gateway, then polls the status
|
|
27
|
+
* endpoint until the challenge is approved, denied, or expires.
|
|
28
|
+
*
|
|
29
|
+
* @param agent - The Agent requesting authorization.
|
|
30
|
+
* @param request - Auth request details (recipient DID, action, metadata).
|
|
31
|
+
* @param gateway - Gateway connection options.
|
|
32
|
+
* @returns Auth result (approved/denied) or error.
|
|
33
|
+
*/
|
|
34
|
+
async function requestAuth(agent, request, gateway) {
|
|
35
|
+
const ttlMs = request.ttlMs ?? DEFAULT_TTL_MS;
|
|
36
|
+
const pollIntervalMs = request.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
37
|
+
// Build the challenge payload
|
|
38
|
+
const payload = {
|
|
39
|
+
recipientDid: request.to,
|
|
40
|
+
action: request.action,
|
|
41
|
+
metadata: request.metadata ?? {},
|
|
42
|
+
ttlMs,
|
|
43
|
+
};
|
|
44
|
+
// Create a signed envelope wrapping the challenge request
|
|
45
|
+
const envelopeResult = await (0, envelope_js_1.createSignedEnvelope)({
|
|
46
|
+
senderDid: agent.did,
|
|
47
|
+
recipientDid: request.to,
|
|
48
|
+
scope: 'xlock:challenge',
|
|
49
|
+
plaintext: new TextEncoder().encode(JSON.stringify(payload)),
|
|
50
|
+
privateKey: agent.identity.privateKey,
|
|
51
|
+
});
|
|
52
|
+
if (!envelopeResult.ok) {
|
|
53
|
+
return (0, shared_1.err)('INVALID_REQUEST');
|
|
54
|
+
}
|
|
55
|
+
// POST to gateway
|
|
56
|
+
let challengeId;
|
|
57
|
+
let expiresAt;
|
|
58
|
+
try {
|
|
59
|
+
const response = await fetch(`${gateway.gatewayUrl}/gateway/auth/challenge`, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: { 'Content-Type': 'application/json' },
|
|
62
|
+
body: JSON.stringify(envelopeResult.value),
|
|
63
|
+
});
|
|
64
|
+
if (response.status === 429)
|
|
65
|
+
return (0, shared_1.err)('RATE_LIMITED');
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
const body = await response.json().catch(() => null);
|
|
68
|
+
const code = body
|
|
69
|
+
?.error?.code;
|
|
70
|
+
return (0, shared_1.err)(code ?? 'INVALID_REQUEST');
|
|
71
|
+
}
|
|
72
|
+
const data = await response.json();
|
|
73
|
+
challengeId = data.challengeId;
|
|
74
|
+
expiresAt = data.expiresAt;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return (0, shared_1.err)('INVALID_REQUEST');
|
|
78
|
+
}
|
|
79
|
+
// Poll for status
|
|
80
|
+
return pollChallengeStatus(challengeId, expiresAt, pollIntervalMs, gateway);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Respond to an incoming auth challenge (approve or deny).
|
|
84
|
+
*
|
|
85
|
+
* Sends a signed response envelope to the gateway.
|
|
86
|
+
*
|
|
87
|
+
* @param agent - The Agent responding to the challenge.
|
|
88
|
+
* @param challengeId - The challenge ID to respond to.
|
|
89
|
+
* @param approved - Whether the user approved the challenge.
|
|
90
|
+
* @param gateway - Gateway connection options.
|
|
91
|
+
* @returns Success or error.
|
|
92
|
+
*/
|
|
93
|
+
async function respondToChallenge(agent, challengeId, approved, gateway) {
|
|
94
|
+
const payload = {
|
|
95
|
+
challengeId,
|
|
96
|
+
approved,
|
|
97
|
+
timestamp: Date.now(),
|
|
98
|
+
};
|
|
99
|
+
// Create a signed envelope for the response
|
|
100
|
+
const envelopeResult = await (0, envelope_js_1.createSignedEnvelope)({
|
|
101
|
+
senderDid: agent.did,
|
|
102
|
+
recipientDid: agent.did, // Self-addressed (gateway verifies recipient match)
|
|
103
|
+
scope: 'xlock:respond',
|
|
104
|
+
plaintext: new TextEncoder().encode(JSON.stringify(payload)),
|
|
105
|
+
privateKey: agent.identity.privateKey,
|
|
106
|
+
});
|
|
107
|
+
if (!envelopeResult.ok) {
|
|
108
|
+
return (0, shared_1.err)('INVALID_REQUEST');
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const response = await fetch(`${gateway.gatewayUrl}/gateway/auth/respond`, {
|
|
112
|
+
method: 'POST',
|
|
113
|
+
headers: { 'Content-Type': 'application/json' },
|
|
114
|
+
body: JSON.stringify({
|
|
115
|
+
...envelopeResult.value,
|
|
116
|
+
// Include response fields at top level for the server
|
|
117
|
+
challengeId,
|
|
118
|
+
approved,
|
|
119
|
+
responseEnvelope: envelopeResult.value,
|
|
120
|
+
}),
|
|
121
|
+
});
|
|
122
|
+
if (!response.ok) {
|
|
123
|
+
const body = await response.json().catch(() => null);
|
|
124
|
+
const code = body
|
|
125
|
+
?.error?.code;
|
|
126
|
+
return (0, shared_1.err)(code ?? 'INVALID_REQUEST');
|
|
127
|
+
}
|
|
128
|
+
return (0, shared_1.ok)(undefined);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return (0, shared_1.err)('INVALID_REQUEST');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Register a callback for incoming auth challenges via WebSocket.
|
|
136
|
+
*
|
|
137
|
+
* The callback fires when an `auth:challenge` message arrives
|
|
138
|
+
* over the gateway WebSocket connection.
|
|
139
|
+
*
|
|
140
|
+
* @param ws - The WebSocket connection to the gateway.
|
|
141
|
+
* @param callback - Handler for incoming challenges.
|
|
142
|
+
* @returns Cleanup function to unregister the listener.
|
|
143
|
+
*/
|
|
144
|
+
function onChallenge(ws, callback) {
|
|
145
|
+
const handler = (event) => {
|
|
146
|
+
try {
|
|
147
|
+
const msg = JSON.parse(event.data);
|
|
148
|
+
if (msg.type === 'auth:challenge' && msg.data) {
|
|
149
|
+
callback(msg.data);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Skip non-JSON messages
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
ws.addEventListener('message', handler);
|
|
157
|
+
return () => ws.removeEventListener('message', handler);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Generate a registration QR code URI for TOTP migration.
|
|
161
|
+
*
|
|
162
|
+
* Creates a `xlock://register` URI that replaces `otpauth://` for
|
|
163
|
+
* asymmetric DID-based registration.
|
|
164
|
+
*
|
|
165
|
+
* @param options - Registration options.
|
|
166
|
+
* @returns The registration URI string.
|
|
167
|
+
*/
|
|
168
|
+
function generateRegistrationQR(options) {
|
|
169
|
+
const params = new URLSearchParams({
|
|
170
|
+
app: options.appName,
|
|
171
|
+
did: options.appDid,
|
|
172
|
+
registry: options.registryUrl,
|
|
173
|
+
callback: options.callbackUrl,
|
|
174
|
+
});
|
|
175
|
+
if (options.appIcon)
|
|
176
|
+
params.set('icon', options.appIcon);
|
|
177
|
+
return `xlock://register?${params.toString()}`;
|
|
178
|
+
}
|
|
179
|
+
/** Poll the gateway for challenge status until resolved or expired. */
|
|
180
|
+
async function pollChallengeStatus(challengeId, expiresAt, pollIntervalMs, gateway) {
|
|
181
|
+
for (let i = 0; i < MAX_POLL_ITERATIONS; i++) {
|
|
182
|
+
if (Date.now() > expiresAt) {
|
|
183
|
+
return (0, shared_1.err)('CHALLENGE_EXPIRED');
|
|
184
|
+
}
|
|
185
|
+
await sleep(pollIntervalMs);
|
|
186
|
+
try {
|
|
187
|
+
const response = await fetch(`${gateway.gatewayUrl}/gateway/auth/status/${challengeId}`, {
|
|
188
|
+
headers: { Authorization: `Bearer ${gateway.accessToken}` },
|
|
189
|
+
});
|
|
190
|
+
if (!response.ok) {
|
|
191
|
+
if (response.status === 404)
|
|
192
|
+
return (0, shared_1.err)('CHALLENGE_NOT_FOUND');
|
|
193
|
+
continue; // Retry on transient errors
|
|
194
|
+
}
|
|
195
|
+
const data = await response.json();
|
|
196
|
+
if (data.status === 'approved') {
|
|
197
|
+
return (0, shared_1.ok)({
|
|
198
|
+
challengeId: data.challengeId,
|
|
199
|
+
approved: true,
|
|
200
|
+
respondedAt: data.respondedAt,
|
|
201
|
+
envelope: data.envelope,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
if (data.status === 'denied') {
|
|
205
|
+
return (0, shared_1.ok)({
|
|
206
|
+
challengeId: data.challengeId,
|
|
207
|
+
approved: false,
|
|
208
|
+
respondedAt: data.respondedAt,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
if (data.status === 'expired') {
|
|
212
|
+
return (0, shared_1.err)('CHALLENGE_EXPIRED');
|
|
213
|
+
}
|
|
214
|
+
// Still pending — continue polling
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// Network error — continue polling
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return (0, shared_1.err)('CHALLENGE_TIMEOUT');
|
|
221
|
+
}
|
|
222
|
+
/** Promise-based sleep. */
|
|
223
|
+
function sleep(ms) {
|
|
224
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
225
|
+
}
|
|
@@ -1 +1,233 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module auto-accept
|
|
4
|
+
* Auto-accept invite on first SDK call for zero-click onboarding.
|
|
5
|
+
*
|
|
6
|
+
* Enables services to accept invites automatically without explicit
|
|
7
|
+
* accept commands. Invite code comes from environment variable.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* // Environment: XBIND_INVITE_CODE=XBD-abc123, XBIND_AUTO_ACCEPT=true
|
|
12
|
+
*
|
|
13
|
+
* // Auto-accept happens on first Agent method call:
|
|
14
|
+
* const agent = Agent.lazy({ name: 'my-service' });
|
|
15
|
+
* await agent.send({ to: partnerDid, payload: data, scope: 'test' });
|
|
16
|
+
* // ↑ Invite auto-accepted before send()
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.AutoAcceptErrorCode = void 0;
|
|
21
|
+
exports.autoAcceptInvite = autoAcceptInvite;
|
|
22
|
+
const shared_1 = require("../_deps/shared/index.js");
|
|
23
|
+
const invite_js_1 = require("./invite.js");
|
|
24
|
+
const trust_registry_js_1 = require("./trust-registry.js");
|
|
25
|
+
const crypto_utils_js_1 = require("./crypto-utils.js");
|
|
26
|
+
const identity_js_1 = require("./identity.js");
|
|
27
|
+
/**
|
|
28
|
+
* Auto-accept error codes.
|
|
29
|
+
*/
|
|
30
|
+
var AutoAcceptErrorCode;
|
|
31
|
+
(function (AutoAcceptErrorCode) {
|
|
32
|
+
AutoAcceptErrorCode["NO_INVITE_CODE"] = "AUTO_ACCEPT_NO_INVITE_CODE";
|
|
33
|
+
AutoAcceptErrorCode["DISABLED"] = "AUTO_ACCEPT_DISABLED";
|
|
34
|
+
AutoAcceptErrorCode["INVITE_FAILED"] = "AUTO_ACCEPT_INVITE_FAILED";
|
|
35
|
+
AutoAcceptErrorCode["REGISTRY_SETUP_FAILED"] = "AUTO_ACCEPT_REGISTRY_SETUP_FAILED";
|
|
36
|
+
})(AutoAcceptErrorCode || (exports.AutoAcceptErrorCode = AutoAcceptErrorCode = {}));
|
|
37
|
+
/**
|
|
38
|
+
* Auto-accept an invite on first SDK call.
|
|
39
|
+
*
|
|
40
|
+
* Reads invite code from config or environment variable `XBIND_INVITE_CODE`.
|
|
41
|
+
* If `XBIND_AUTO_ACCEPT` is false, returns error with code DISABLED.
|
|
42
|
+
*
|
|
43
|
+
* When successful:
|
|
44
|
+
* 1. Fetches invite details from invite server
|
|
45
|
+
* 2. Adds inviter to trust registry
|
|
46
|
+
* 3. Auto-detects registry endpoint from invite metadata (if present)
|
|
47
|
+
* 4. Marks invite as accepted
|
|
48
|
+
*
|
|
49
|
+
* This is called internally by `Agent.lazy()` before the first send/receive.
|
|
50
|
+
*
|
|
51
|
+
* @param config - Auto-accept configuration
|
|
52
|
+
* @param acceptorInfo - Acceptor service info (DID, endpoint, publicKey)
|
|
53
|
+
* @returns Accept details or error
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* // Manual usage (typically called internally by Agent.lazy):
|
|
58
|
+
* const result = await autoAcceptInvite(
|
|
59
|
+
* { inviteCode: 'XBD-abc123' },
|
|
60
|
+
* {
|
|
61
|
+
* name: 'my-service',
|
|
62
|
+
* did: 'did:key:z6Mk...',
|
|
63
|
+
* endpoint: 'https://my-service.com',
|
|
64
|
+
* publicKey: '...',
|
|
65
|
+
* }
|
|
66
|
+
* );
|
|
67
|
+
*
|
|
68
|
+
* if (result.ok) {
|
|
69
|
+
* console.log('Auto-accepted invite from:', result.value.from.name);
|
|
70
|
+
* }
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
async function autoAcceptInvite(config, acceptorInfo) {
|
|
74
|
+
// Step 1: Check if auto-accept is enabled
|
|
75
|
+
const autoAcceptEnabled = config.enabled ?? getEnvFlag('XBIND_AUTO_ACCEPT', true);
|
|
76
|
+
if (!autoAcceptEnabled) {
|
|
77
|
+
return (0, shared_1.err)({
|
|
78
|
+
code: AutoAcceptErrorCode.DISABLED,
|
|
79
|
+
message: 'Auto-accept is disabled',
|
|
80
|
+
hint: 'Set XBIND_AUTO_ACCEPT=true or pass { enabled: true } in config',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// Step 2: Get invite code
|
|
84
|
+
const inviteCode = config.inviteCode ?? getEnv('XBIND_INVITE_CODE');
|
|
85
|
+
if (!inviteCode) {
|
|
86
|
+
return (0, shared_1.err)({
|
|
87
|
+
code: AutoAcceptErrorCode.NO_INVITE_CODE,
|
|
88
|
+
message: 'No invite code provided',
|
|
89
|
+
hint: 'Set XBIND_INVITE_CODE environment variable or pass inviteCode in config',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
// Step 3: Fetch invite details
|
|
93
|
+
const inviteService = new invite_js_1.InviteService({
|
|
94
|
+
inviteApiUrl: config.inviteApiUrl ?? getEnv('XBIND_INVITE_API_URL') ?? 'https://xbind.to',
|
|
95
|
+
});
|
|
96
|
+
const inviteUrl = normalizeInviteCode(inviteCode);
|
|
97
|
+
const inviteResult = await inviteService.get(inviteUrl);
|
|
98
|
+
if (!inviteResult.ok) {
|
|
99
|
+
return (0, shared_1.err)({
|
|
100
|
+
code: AutoAcceptErrorCode.INVITE_FAILED,
|
|
101
|
+
message: `Failed to fetch invite: ${inviteResult.error.message}`,
|
|
102
|
+
hint: inviteResult.error.hint,
|
|
103
|
+
cause: inviteResult.error,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const invite = inviteResult.value;
|
|
107
|
+
// Step 4: Add inviter to trust registry
|
|
108
|
+
const registry = config.registry ?? await autoConfigureRegistry(invite);
|
|
109
|
+
try {
|
|
110
|
+
// Import public key from base64
|
|
111
|
+
const publicKeyBytes = (0, crypto_utils_js_1.fromBase64)(invite.from.publicKey);
|
|
112
|
+
const publicKeyResult = await (0, identity_js_1.importPublicKey)(publicKeyBytes);
|
|
113
|
+
if (!publicKeyResult.ok) {
|
|
114
|
+
return (0, shared_1.err)({
|
|
115
|
+
code: AutoAcceptErrorCode.REGISTRY_SETUP_FAILED,
|
|
116
|
+
message: 'Invalid inviter public key',
|
|
117
|
+
hint: 'The invite may be corrupted',
|
|
118
|
+
cause: publicKeyResult.error,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
// Add to registry
|
|
122
|
+
const addResult = await registry.register(invite.from.did, publicKeyBytes, invite.from.name, invite.permissions, invite.from.x25519PublicKey ? (0, crypto_utils_js_1.fromBase64)(invite.from.x25519PublicKey) : undefined, invite.from.mlKemPublicKey ? (0, crypto_utils_js_1.fromBase64)(invite.from.mlKemPublicKey) : undefined, undefined, // mlDsaPublicKey (not in invite yet)
|
|
123
|
+
false);
|
|
124
|
+
if (!addResult.ok) {
|
|
125
|
+
// If already registered, that's OK (idempotent)
|
|
126
|
+
if (addResult.error !== 'ALREADY_REGISTERED') {
|
|
127
|
+
return (0, shared_1.err)({
|
|
128
|
+
code: AutoAcceptErrorCode.REGISTRY_SETUP_FAILED,
|
|
129
|
+
message: `Failed to add inviter to registry: ${addResult.error}`,
|
|
130
|
+
cause: addResult.error,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
return (0, shared_1.err)({
|
|
137
|
+
code: AutoAcceptErrorCode.REGISTRY_SETUP_FAILED,
|
|
138
|
+
message: 'Failed to configure trust registry',
|
|
139
|
+
cause: error,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
// Step 5: Accept the invite (marks as accepted on server)
|
|
143
|
+
const acceptResult = await inviteService.accept({ inviteUrl, acceptor: acceptorInfo });
|
|
144
|
+
if (!acceptResult.ok) {
|
|
145
|
+
// Non-fatal: invite acceptance is for tracking/notification only
|
|
146
|
+
// The connection is still established locally via registry add
|
|
147
|
+
}
|
|
148
|
+
return (0, shared_1.ok)({
|
|
149
|
+
invite,
|
|
150
|
+
from: invite.from,
|
|
151
|
+
registryUrl: extractRegistryUrl(invite),
|
|
152
|
+
registryAutoconfigured: config.registry === undefined,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Auto-configure trust registry from invite metadata.
|
|
157
|
+
*
|
|
158
|
+
* Attempts to extract registry URL from invite. If found, creates HttpTrustRegistry.
|
|
159
|
+
* Otherwise falls back to MemoryTrustRegistry.
|
|
160
|
+
*
|
|
161
|
+
* @param invite - Invite details
|
|
162
|
+
* @returns Trust registry instance
|
|
163
|
+
*/
|
|
164
|
+
async function autoConfigureRegistry(invite) {
|
|
165
|
+
const registryUrl = extractRegistryUrl(invite);
|
|
166
|
+
if (registryUrl) {
|
|
167
|
+
return new trust_registry_js_1.HttpTrustRegistry({ baseUrl: registryUrl });
|
|
168
|
+
}
|
|
169
|
+
return new trust_registry_js_1.MemoryTrustRegistry();
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Extract registry URL from invite metadata.
|
|
173
|
+
*
|
|
174
|
+
* Checks for registry URL in invite.from.endpoint or custom metadata fields.
|
|
175
|
+
*
|
|
176
|
+
* @param invite - Invite details
|
|
177
|
+
* @returns Registry URL or undefined
|
|
178
|
+
*/
|
|
179
|
+
function extractRegistryUrl(invite) {
|
|
180
|
+
// Check if endpoint is a registry URL (heuristic: contains /registry or /trust)
|
|
181
|
+
if (invite.from.endpoint.includes('/registry') || invite.from.endpoint.includes('/trust')) {
|
|
182
|
+
return invite.from.endpoint;
|
|
183
|
+
}
|
|
184
|
+
// Future: check invite.metadata.registryUrl when invite system adds it
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Normalize invite code to full URL.
|
|
189
|
+
*
|
|
190
|
+
* If code is already a URL, returns as-is.
|
|
191
|
+
* If code is short form (e.g., 'XBD-abc123'), converts to https://xbind.to/invite/{code}.
|
|
192
|
+
*
|
|
193
|
+
* @param code - Invite code or URL
|
|
194
|
+
* @returns Full invite URL
|
|
195
|
+
*/
|
|
196
|
+
function normalizeInviteCode(code) {
|
|
197
|
+
if (code.startsWith('http://') || code.startsWith('https://')) {
|
|
198
|
+
return code;
|
|
199
|
+
}
|
|
200
|
+
// Short form: XBD-abc123 or just abc123
|
|
201
|
+
const cleanCode = code.replace(/^XBD-/i, '');
|
|
202
|
+
return `https://xbind.to/invite/${cleanCode}`;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Get environment variable value.
|
|
206
|
+
*
|
|
207
|
+
* @param key - Environment variable name
|
|
208
|
+
* @param defaultValue - Default value if not set
|
|
209
|
+
* @returns Environment variable value or default
|
|
210
|
+
*/
|
|
211
|
+
function getEnv(key, defaultValue) {
|
|
212
|
+
// SAFETY: Check for Node.js environment before accessing process
|
|
213
|
+
if (typeof process !== 'undefined' && typeof process.env !== 'undefined') {
|
|
214
|
+
return process.env[key] ?? defaultValue;
|
|
215
|
+
}
|
|
216
|
+
return defaultValue;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Get environment variable as boolean flag.
|
|
220
|
+
*
|
|
221
|
+
* Treats 'true', '1', 'yes' as true. Everything else as false.
|
|
222
|
+
*
|
|
223
|
+
* @param key - Environment variable name
|
|
224
|
+
* @param defaultValue - Default value if not set
|
|
225
|
+
* @returns Boolean flag
|
|
226
|
+
*/
|
|
227
|
+
function getEnvFlag(key, defaultValue) {
|
|
228
|
+
const value = getEnv(key);
|
|
229
|
+
if (value === undefined)
|
|
230
|
+
return defaultValue;
|
|
231
|
+
const normalized = value.toLowerCase().trim();
|
|
232
|
+
return normalized === 'true' || normalized === '1' || normalized === 'yes';
|
|
233
|
+
}
|
|
@@ -1 +1,207 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* XorIDA Backup Configuration for Key Splitting
|
|
4
|
+
*
|
|
5
|
+
* Provides default backup configuration (k=2, n=3) and utilities for
|
|
6
|
+
* splitting cryptographic keys across multiple shares using information-
|
|
7
|
+
* theoretic threshold secret sharing.
|
|
8
|
+
*
|
|
9
|
+
* @module backup-config
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.DEFAULT_BACKUP_CONFIG = void 0;
|
|
13
|
+
exports.validateBackupConfig = validateBackupConfig;
|
|
14
|
+
exports.splitKeyWithBackup = splitKeyWithBackup;
|
|
15
|
+
exports.reconstructKeyFromBackup = reconstructKeyFromBackup;
|
|
16
|
+
const shared_1 = require("../_deps/shared/index.js");
|
|
17
|
+
const crypto_utils_js_1 = require("./crypto-utils.js");
|
|
18
|
+
/* ── Constants ── */
|
|
19
|
+
/**
|
|
20
|
+
* Default backup configuration: 2-of-3 threshold sharing.
|
|
21
|
+
*
|
|
22
|
+
* - 3 shares generated
|
|
23
|
+
* - Any 2 shares can reconstruct the key
|
|
24
|
+
* - Lose 1 share and still recover (fault tolerance)
|
|
25
|
+
* - Information-theoretic security (each share reveals zero information)
|
|
26
|
+
*/
|
|
27
|
+
exports.DEFAULT_BACKUP_CONFIG = {
|
|
28
|
+
threshold: 2,
|
|
29
|
+
totalShares: 3,
|
|
30
|
+
};
|
|
31
|
+
/* ── Validation ── */
|
|
32
|
+
/**
|
|
33
|
+
* Validate backup configuration parameters.
|
|
34
|
+
*
|
|
35
|
+
* Rules:
|
|
36
|
+
* - threshold must be >= 2 (single share = no threshold)
|
|
37
|
+
* - totalShares must be >= threshold
|
|
38
|
+
* - totalShares must be <= 255 (XorIDA limit)
|
|
39
|
+
*
|
|
40
|
+
* @param config - Backup configuration to validate.
|
|
41
|
+
* @returns Ok if valid, error otherwise.
|
|
42
|
+
*/
|
|
43
|
+
function validateBackupConfig(config) {
|
|
44
|
+
if (config.threshold < 2) {
|
|
45
|
+
return (0, shared_1.err)('INVALID_CONFIG');
|
|
46
|
+
}
|
|
47
|
+
if (config.totalShares < config.threshold) {
|
|
48
|
+
return (0, shared_1.err)('INVALID_CONFIG');
|
|
49
|
+
}
|
|
50
|
+
if (config.totalShares > 255) {
|
|
51
|
+
return (0, shared_1.err)('INVALID_CONFIG');
|
|
52
|
+
}
|
|
53
|
+
return (0, shared_1.ok)(undefined);
|
|
54
|
+
}
|
|
55
|
+
/* ── Key Splitting ── */
|
|
56
|
+
/**
|
|
57
|
+
* Split a cryptographic key into backup shares using XorIDA.
|
|
58
|
+
*
|
|
59
|
+
* The key is padded, split via information-theoretic threshold sharing,
|
|
60
|
+
* and returned as BackupShare objects with HMAC integrity protection.
|
|
61
|
+
*
|
|
62
|
+
* Any `threshold` shares can reconstruct the original key. Each share
|
|
63
|
+
* reveals zero information about the key (information-theoretic security).
|
|
64
|
+
*
|
|
65
|
+
* @param key - The key to split (32 or 64 bytes typical).
|
|
66
|
+
* @param config - Backup configuration (defaults to 2-of-3).
|
|
67
|
+
* @returns Array of backup shares or error.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* import { splitKeyWithBackup, DEFAULT_BACKUP_CONFIG } from '@private.me/xbind';
|
|
72
|
+
*
|
|
73
|
+
* const key = crypto.getRandomValues(new Uint8Array(32));
|
|
74
|
+
*
|
|
75
|
+
* // Use defaults (2-of-3)
|
|
76
|
+
* const shares = await splitKeyWithBackup(key);
|
|
77
|
+
*
|
|
78
|
+
* // Custom config (3-of-5)
|
|
79
|
+
* const shares2 = await splitKeyWithBackup(key, {
|
|
80
|
+
* threshold: 3,
|
|
81
|
+
* totalShares: 5
|
|
82
|
+
* });
|
|
83
|
+
*
|
|
84
|
+
* if (shares.ok) {
|
|
85
|
+
* // Store shares in separate locations
|
|
86
|
+
* shares.value.forEach((share, i) => {
|
|
87
|
+
* storeShare(`backup-${i}.json`, JSON.stringify(share));
|
|
88
|
+
* });
|
|
89
|
+
* }
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
async function splitKeyWithBackup(key, config = exports.DEFAULT_BACKUP_CONFIG) {
|
|
93
|
+
const validation = validateBackupConfig(config);
|
|
94
|
+
if (!validation.ok)
|
|
95
|
+
return validation;
|
|
96
|
+
if (key.length === 0) {
|
|
97
|
+
return (0, shared_1.err)('INVALID_KEY_LENGTH');
|
|
98
|
+
}
|
|
99
|
+
const n = config.totalShares;
|
|
100
|
+
const k = config.threshold;
|
|
101
|
+
const p = (0, crypto_utils_js_1.nextOddPrime)(n);
|
|
102
|
+
const blockSize = p - 1;
|
|
103
|
+
// Pad to block size
|
|
104
|
+
const padded = (0, crypto_utils_js_1.pkcs7Pad)(key, blockSize);
|
|
105
|
+
// Generate HMAC for integrity verification
|
|
106
|
+
const { key: hmacKey, signature: hmacSig } = await (0, crypto_utils_js_1.generateHMAC)(padded);
|
|
107
|
+
const hmacKeyB64 = (0, crypto_utils_js_1.toBase64)(hmacKey);
|
|
108
|
+
const hmacSigB64 = (0, crypto_utils_js_1.toBase64)(hmacSig);
|
|
109
|
+
// Split via XorIDA
|
|
110
|
+
let shareArrays;
|
|
111
|
+
try {
|
|
112
|
+
shareArrays = (0, crypto_utils_js_1.splitXorIDA)(padded, n, k);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return (0, shared_1.err)('SPLIT_FAILED');
|
|
116
|
+
}
|
|
117
|
+
// Package as BackupShare objects
|
|
118
|
+
const shares = shareArrays.map((data, index) => ({
|
|
119
|
+
index,
|
|
120
|
+
data: (0, crypto_utils_js_1.toBase64)(data),
|
|
121
|
+
total: n,
|
|
122
|
+
threshold: k,
|
|
123
|
+
hmacKey: hmacKeyB64,
|
|
124
|
+
hmacSig: hmacSigB64,
|
|
125
|
+
}));
|
|
126
|
+
return (0, shared_1.ok)(shares);
|
|
127
|
+
}
|
|
128
|
+
/* ── Key Reconstruction ── */
|
|
129
|
+
/**
|
|
130
|
+
* Reconstruct a cryptographic key from backup shares.
|
|
131
|
+
*
|
|
132
|
+
* Requires at least `threshold` shares. Verifies HMAC before returning
|
|
133
|
+
* the reconstructed key to prevent tampering.
|
|
134
|
+
*
|
|
135
|
+
* @param shares - Backup shares (must be >= threshold).
|
|
136
|
+
* @returns Reconstructed key or error.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```typescript
|
|
140
|
+
* import { reconstructKeyFromBackup } from '@private.me/xbind';
|
|
141
|
+
*
|
|
142
|
+
* // Load shares from storage
|
|
143
|
+
* const share0 = JSON.parse(loadShare('backup-0.json'));
|
|
144
|
+
* const share1 = JSON.parse(loadShare('backup-1.json'));
|
|
145
|
+
*
|
|
146
|
+
* // Reconstruct from any 2 shares (threshold=2)
|
|
147
|
+
* const key = await reconstructKeyFromBackup([share0, share1]);
|
|
148
|
+
*
|
|
149
|
+
* if (key.ok) {
|
|
150
|
+
* // Use reconstructed key
|
|
151
|
+
* const agent = await Agent.fromSeed(key.value, opts);
|
|
152
|
+
* } else {
|
|
153
|
+
* console.error('Reconstruction failed:', key.error);
|
|
154
|
+
* }
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
async function reconstructKeyFromBackup(shares) {
|
|
158
|
+
if (shares.length === 0) {
|
|
159
|
+
return (0, shared_1.err)('INSUFFICIENT_SHARES');
|
|
160
|
+
}
|
|
161
|
+
const threshold = shares[0].threshold;
|
|
162
|
+
const total = shares[0].total;
|
|
163
|
+
if (shares.length < threshold) {
|
|
164
|
+
return (0, shared_1.err)('INSUFFICIENT_SHARES');
|
|
165
|
+
}
|
|
166
|
+
// Use first `threshold` shares
|
|
167
|
+
const usedShares = shares.slice(0, threshold);
|
|
168
|
+
// Decode share data
|
|
169
|
+
let shareData;
|
|
170
|
+
try {
|
|
171
|
+
shareData = usedShares.map((s) => (0, crypto_utils_js_1.fromBase64)(s.data));
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return (0, shared_1.err)('INVALID_SHARE_DATA');
|
|
175
|
+
}
|
|
176
|
+
const indices = usedShares.map((s) => s.index);
|
|
177
|
+
// Reconstruct padded key
|
|
178
|
+
let padded;
|
|
179
|
+
try {
|
|
180
|
+
padded = (0, crypto_utils_js_1.reconstructXorIDA)(shareData, indices, total, threshold);
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return (0, shared_1.err)('RECONSTRUCT_FAILED');
|
|
184
|
+
}
|
|
185
|
+
// Verify HMAC
|
|
186
|
+
let hmacKey;
|
|
187
|
+
let hmacSig;
|
|
188
|
+
try {
|
|
189
|
+
hmacKey = (0, crypto_utils_js_1.fromBase64)(usedShares[0].hmacKey);
|
|
190
|
+
hmacSig = (0, crypto_utils_js_1.fromBase64)(usedShares[0].hmacSig);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return (0, shared_1.err)('INVALID_SHARE_DATA');
|
|
194
|
+
}
|
|
195
|
+
const hmacValid = await (0, crypto_utils_js_1.verifyHMAC)(hmacKey, padded, hmacSig);
|
|
196
|
+
if (!hmacValid) {
|
|
197
|
+
return (0, shared_1.err)('HMAC_VERIFICATION_FAILED');
|
|
198
|
+
}
|
|
199
|
+
// Unpad to recover original key
|
|
200
|
+
const p = (0, crypto_utils_js_1.nextOddPrime)(total);
|
|
201
|
+
const blockSize = p - 1;
|
|
202
|
+
const unpadResult = (0, crypto_utils_js_1.pkcs7Unpad)(padded, blockSize);
|
|
203
|
+
if (!unpadResult.ok) {
|
|
204
|
+
return (0, shared_1.err)('RECONSTRUCT_FAILED');
|
|
205
|
+
}
|
|
206
|
+
return (0, shared_1.ok)(unpadResult.value);
|
|
207
|
+
}
|