ftown 0.20.9 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/dist/attention-events.d.ts +1 -1
  2. package/dist/attention-store.d.ts +3 -0
  3. package/dist/attention-store.js +25 -1
  4. package/dist/attention-store.js.map +1 -1
  5. package/dist/centrifugo-client.d.ts +28 -11
  6. package/dist/centrifugo-client.js +84 -70
  7. package/dist/centrifugo-client.js.map +1 -1
  8. package/dist/cli-client.d.ts +11 -1
  9. package/dist/cli-client.js +23 -3
  10. package/dist/cli-client.js.map +1 -1
  11. package/dist/command-rpc.d.ts +2 -0
  12. package/dist/command-rpc.js +59 -17
  13. package/dist/command-rpc.js.map +1 -1
  14. package/dist/identity-reset.d.ts +4 -1
  15. package/dist/identity-reset.js +54 -6
  16. package/dist/identity-reset.js.map +1 -1
  17. package/dist/index.js +194 -288
  18. package/dist/index.js.map +1 -1
  19. package/dist/ledger-pairing-client.d.ts +28 -0
  20. package/dist/ledger-pairing-client.js +154 -0
  21. package/dist/ledger-pairing-client.js.map +1 -0
  22. package/dist/ledger-relay.d.ts +159 -0
  23. package/dist/ledger-relay.js +595 -0
  24. package/dist/ledger-relay.js.map +1 -0
  25. package/dist/ledger-reoffer.d.ts +56 -0
  26. package/dist/ledger-reoffer.js +210 -0
  27. package/dist/ledger-reoffer.js.map +1 -0
  28. package/dist/ledger-startup.d.ts +176 -0
  29. package/dist/ledger-startup.js +233 -0
  30. package/dist/ledger-startup.js.map +1 -0
  31. package/dist/local-api-server.d.ts +3 -0
  32. package/dist/local-api-server.js +68 -6
  33. package/dist/local-api-server.js.map +1 -1
  34. package/dist/mail-delivery.d.ts +26 -3
  35. package/dist/mail-delivery.js +65 -7
  36. package/dist/mail-delivery.js.map +1 -1
  37. package/dist/preview-protocol.d.ts +19 -3
  38. package/dist/preview-protocol.js +14 -0
  39. package/dist/preview-protocol.js.map +1 -1
  40. package/dist/relay-token.d.ts +45 -0
  41. package/dist/relay-token.js +138 -0
  42. package/dist/relay-token.js.map +1 -0
  43. package/dist/revocation-gating.d.ts +0 -13
  44. package/dist/revocation-gating.js +0 -16
  45. package/dist/revocation-gating.js.map +1 -1
  46. package/dist/security/bridge/identity.d.ts +2 -47
  47. package/dist/security/bridge/identity.js +3 -3
  48. package/dist/security/bridge/identity.js.map +1 -1
  49. package/dist/security/bridge/ledger-identity.d.ts +48 -0
  50. package/dist/security/bridge/ledger-identity.js +342 -0
  51. package/dist/security/bridge/ledger-identity.js.map +1 -0
  52. package/dist/security/bridge/ledger-rpc.d.ts +34 -0
  53. package/dist/security/bridge/ledger-rpc.js +58 -0
  54. package/dist/security/bridge/ledger-rpc.js.map +1 -0
  55. package/dist/security/bridge/ledger-store.d.ts +153 -0
  56. package/dist/security/bridge/ledger-store.js +825 -0
  57. package/dist/security/bridge/ledger-store.js.map +1 -0
  58. package/dist/security/bridge/metadata.d.ts +2 -2
  59. package/dist/security/bridge/permissions.js +4 -3
  60. package/dist/security/bridge/permissions.js.map +1 -1
  61. package/dist/security/bridge/policy.d.ts +43 -23
  62. package/dist/security/bridge/policy.js +214 -146
  63. package/dist/security/bridge/policy.js.map +1 -1
  64. package/dist/security/bridge/security.d.ts +212 -33
  65. package/dist/security/bridge/security.js +437 -165
  66. package/dist/security/bridge/security.js.map +1 -1
  67. package/dist/security/crypto.js +1 -1
  68. package/dist/security/crypto.js.map +1 -1
  69. package/dist/security/ledger/announce.d.ts +32 -0
  70. package/dist/security/ledger/announce.js +101 -0
  71. package/dist/security/ledger/announce.js.map +1 -0
  72. package/dist/security/ledger/channels.d.ts +5 -0
  73. package/dist/security/ledger/channels.js +21 -0
  74. package/dist/security/ledger/channels.js.map +1 -0
  75. package/dist/security/ledger/ledger.d.ts +154 -0
  76. package/dist/security/ledger/ledger.js +878 -0
  77. package/dist/security/ledger/ledger.js.map +1 -0
  78. package/dist/security/ledger/pairing.d.ts +81 -0
  79. package/dist/security/ledger/pairing.js +414 -0
  80. package/dist/security/ledger/pairing.js.map +1 -0
  81. package/dist/security/ledger/proof.d.ts +36 -0
  82. package/dist/security/ledger/proof.js +193 -0
  83. package/dist/security/ledger/proof.js.map +1 -0
  84. package/dist/security/ledger/protocol.d.ts +113 -0
  85. package/dist/security/ledger/protocol.js +16 -0
  86. package/dist/security/ledger/protocol.js.map +1 -0
  87. package/dist/security/ledger/testing.d.ts +27 -0
  88. package/dist/security/ledger/testing.js +70 -0
  89. package/dist/security/ledger/testing.js.map +1 -0
  90. package/dist/security/ledger/webauthn.d.ts +12 -0
  91. package/dist/security/ledger/webauthn.js +147 -0
  92. package/dist/security/ledger/webauthn.js.map +1 -0
  93. package/dist/security/protocol.d.ts +5 -10
  94. package/dist/security-cli.d.ts +5 -0
  95. package/dist/security-cli.js +28 -16
  96. package/dist/security-cli.js.map +1 -1
  97. package/dist/session-controller.d.ts +9 -3
  98. package/dist/session-controller.js +120 -20
  99. package/dist/session-controller.js.map +1 -1
  100. package/dist/session-inbox-protocol.d.ts +2 -0
  101. package/dist/session-preview.d.ts +20 -5
  102. package/dist/session-preview.js +185 -37
  103. package/dist/session-preview.js.map +1 -1
  104. package/dist/session-store.js +19 -1
  105. package/dist/session-store.js.map +1 -1
  106. package/dist/sessions-commands.js +41 -7
  107. package/dist/sessions-commands.js.map +1 -1
  108. package/dist/token-provider.d.ts +39 -0
  109. package/dist/token-provider.js +101 -0
  110. package/dist/token-provider.js.map +1 -0
  111. package/dist/types.d.ts +1 -1
  112. package/opencode-plugin/ftown.js +15 -2
  113. package/package.json +2 -2
  114. package/skills/ftown/SKILL.md +18 -7
  115. package/skills/ftown/references/sessions.md +38 -12
  116. package/dist/bridge-auth.d.ts +0 -31
  117. package/dist/bridge-auth.js +0 -70
  118. package/dist/bridge-auth.js.map +0 -1
  119. package/dist/pairing-client.d.ts +0 -62
  120. package/dist/pairing-client.js +0 -469
  121. package/dist/pairing-client.js.map +0 -1
  122. package/dist/rotating-token-refresher.d.ts +0 -42
  123. package/dist/rotating-token-refresher.js +0 -91
  124. package/dist/rotating-token-refresher.js.map +0 -1
  125. package/dist/security/account-crypto.d.ts +0 -33
  126. package/dist/security/account-crypto.js +0 -649
  127. package/dist/security/account-crypto.js.map +0 -1
  128. package/dist/security/account-protocol.d.ts +0 -228
  129. package/dist/security/account-protocol.js +0 -2
  130. package/dist/security/account-protocol.js.map +0 -1
  131. package/dist/security/bridge/account-client.d.ts +0 -7
  132. package/dist/security/bridge/account-client.js +0 -101
  133. package/dist/security/bridge/account-client.js.map +0 -1
  134. package/dist/security/bridge/account-state.d.ts +0 -105
  135. package/dist/security/bridge/account-state.js +0 -79
  136. package/dist/security/bridge/account-state.js.map +0 -1
  137. package/dist/security/bridge/pending-approval.d.ts +0 -21
  138. package/dist/security/bridge/pending-approval.js +0 -80
  139. package/dist/security/bridge/pending-approval.js.map +0 -1
  140. package/dist/security/bridge/pending-enrollment.d.ts +0 -33
  141. package/dist/security/bridge/pending-enrollment.js +0 -177
  142. package/dist/security/bridge/pending-enrollment.js.map +0 -1
  143. package/dist/security/bridge/revocation.d.ts +0 -14
  144. package/dist/security/bridge/revocation.js +0 -40
  145. package/dist/security/bridge/revocation.js.map +0 -1
  146. package/dist/security/pending-approval-crypto.d.ts +0 -29
  147. package/dist/security/pending-approval-crypto.js +0 -716
  148. package/dist/security/pending-approval-crypto.js.map +0 -1
  149. package/dist/security/pending-approval-protocol.d.ts +0 -329
  150. package/dist/security/pending-approval-protocol.js +0 -2
  151. package/dist/security/pending-approval-protocol.js.map +0 -1
  152. package/dist/security/security-nudge.d.ts +0 -31
  153. package/dist/security/security-nudge.js +0 -98
  154. package/dist/security/security-nudge.js.map +0 -1
@@ -0,0 +1,48 @@
1
+ import type { EndpointPublic } from '../protocol.js';
2
+ export interface LedgerBridgeIdentity {
3
+ /** ECDH P-256 endpoint key pair for HPKE; the private key is non-extractable. */
4
+ keyPair: CryptoKeyPair;
5
+ /** Raw uncompressed ECDH point, base64url: BridgePairRequest.endpointPublicKey. */
6
+ endpointPublicKey: string;
7
+ /** base64url SHA-256 of the raw endpoint point: EndpointPublic.id. */
8
+ endpointId: string;
9
+ /** Raw uncompressed ECDSA point, base64url: BridgePairRequest/LedgerBridge.authPublicKey. */
10
+ authPublicKey: string;
11
+ /** Non-extractable ECDSA P-256 signing key for signAuthProof. */
12
+ authPrivateKey: CryptoKey;
13
+ /** The bound endpoint, or null before pairing. With `keyPair` it is the bridge's EndpointIdentity. */
14
+ endpoint: EndpointPublic | null;
15
+ /** endpoint.ownerId once bound, else null. */
16
+ accountId: string | null;
17
+ }
18
+ /** What is on disk, without importing keys. A corrupted file throws BridgeSecurityError('key_unavailable'). */
19
+ export type LedgerIdentityFileInfo = {
20
+ state: 'absent';
21
+ accountId: null;
22
+ } | {
23
+ state: 'ready';
24
+ accountId: string | null;
25
+ };
26
+ /** Reports the v2 identity file's state without importing keys. Throws BridgeSecurityError('key_unavailable') for a corrupted file. */
27
+ export declare function inspectIdentityFile(dataDir: string): Promise<LedgerIdentityFileInfo>;
28
+ /**
29
+ * True iff the data dir holds v1 ACCOUNT state: `refresh-token`, `security/account-enrollment.json`,
30
+ * `security/pending-approval-status.json` or `security/pending-approval-confirmation.json` exists, or `security/bridge-identity.json`
31
+ * has a readable endpoint owner that is not `solo:` (Solo's identity is never reported). An unreadable bridge-identity.json is not
32
+ * reported: its owner cannot be told apart from Solo's. Reads only; never modifies anything.
33
+ */
34
+ export declare function hasLegacyAccountState(dataDir: string): boolean;
35
+ /** Loads the identity without creating one: null when absent. Throws BridgeSecurityError('key_unavailable') for a corrupted file. */
36
+ export declare function readLedgerIdentity(dataDir: string): Promise<LedgerBridgeIdentity | null>;
37
+ /**
38
+ * Loads the identity, creating fresh keys only when the file is absent. Throws BridgeSecurityError: key_unavailable for an unreadable
39
+ * or corrupted file (never replaced), storage_unavailable when the new file cannot be written.
40
+ */
41
+ export declare function loadOrCreateLedgerIdentity(dataDir: string): Promise<LedgerBridgeIdentity>;
42
+ /**
43
+ * Binds the identity to `accountId` once: sets endpoint {version:1, id, ownerId: accountId, kind:'bridge', publicKey}. Idempotent for
44
+ * the same account. Throws BridgeSecurityError: wrong_context for a different (or malformed) accountId, key_unavailable when there is
45
+ * no valid identity to bind, storage_unavailable when the file cannot be written. Binds are serialized within this process only: the
46
+ * bridge's single-instance guarantee keeps a second process from binding concurrently.
47
+ */
48
+ export declare function bindLedgerIdentity(dataDir: string, accountId: string): Promise<LedgerBridgeIdentity>;
@@ -0,0 +1,342 @@
1
+ /*
2
+ * OS-local keys of a ledger (v2) bridge: `<dataDir>/security/ledger-identity.json`, version 2.
3
+ * - An ECDH P-256 endpoint key (HPKE) and an ECDSA P-256 auth key (AuthProof), both stored as JWK, file mode 0600.
4
+ * - Before pairing the file has keys but no endpoint; bindLedgerIdentity sets {version:1, id, ownerId: accountId, kind:'bridge',
5
+ * publicKey} exactly once and never rebinds to another account.
6
+ * - Absence alone creates keys. A corrupted or unreadable file is never replaced: it throws BridgeSecurityError('key_unavailable').
7
+ * - Solo and --local keep their v1 `security/bridge-identity.json` (identity.ts); this module never reads, writes or deletes it, except
8
+ * for hasLegacyAccountState, which only reads its owner to tell a v1 ACCOUNT identity from a Solo one.
9
+ * - Writers are serialized within this process; across processes the bridge's single-instance guarantee is relied on (creation is
10
+ * still exclusive via link(2), but two processes binding at once could each believe they won).
11
+ */
12
+ import { randomUUID } from 'node:crypto';
13
+ import { existsSync, readFileSync } from 'node:fs';
14
+ import { link, mkdir, open, readdir, rename, rm } from 'node:fs/promises';
15
+ import { basename, dirname, join, resolve } from 'node:path';
16
+ import { z } from 'zod';
17
+ import { exportPublicKey, importPublicKey } from '../crypto.js';
18
+ import { generateAuthKey } from '../ledger/proof.js';
19
+ import { BridgeSecurityError } from './errors.js';
20
+ import { endpointSchema } from './metadata.js';
21
+ const FILE_NAME = 'ledger-identity.json';
22
+ const MAX_FILE_BYTES = 64 * 1024;
23
+ const ECDH = { name: 'ECDH', namedCurve: 'P-256' };
24
+ const ECDSA = { name: 'ECDSA', namedCurve: 'P-256' };
25
+ const SIGN = { name: 'ECDSA', hash: 'SHA-256' };
26
+ /** v1 account-mode state (never Solo's): any of these means the data dir still holds a v1 account bridge. */
27
+ const LEGACY_ACCOUNT_FILES = [
28
+ ['refresh-token'], ['security', 'account-enrollment.json'], ['security', 'pending-approval-status.json'],
29
+ ['security', 'pending-approval-confirmation.json'],
30
+ ];
31
+ const TEMPORARY = /^\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.tmp$/;
32
+ const jwkSchema = z.strictObject({
33
+ kty: z.literal('EC'), crv: z.literal('P-256'), x: z.string().max(64), y: z.string().max(64), d: z.string().max(64),
34
+ key_ops: z.array(z.string().max(32)).max(8).optional(), ext: z.boolean().optional(),
35
+ });
36
+ const pointSchema = z.string().regex(/^[A-Za-z0-9_-]{87}$/);
37
+ const recordSchema = z.strictObject({
38
+ version: z.literal(2),
39
+ endpoint: endpointSchema.optional(),
40
+ privateKey: jwkSchema,
41
+ endpointPublicKey: pointSchema,
42
+ authPublicKey: pointSchema,
43
+ authPrivateKey: jwkSchema,
44
+ }).superRefine((record, context) => {
45
+ if (record.endpointPublicKey === record.authPublicKey)
46
+ context.addIssue({ code: 'custom', message: 'Shared identity key' });
47
+ const endpoint = record.endpoint;
48
+ if (endpoint !== undefined && (endpoint.kind !== 'bridge' || endpoint.publicKey !== record.endpointPublicKey || !isAccountId(endpoint.ownerId))) {
49
+ context.addIssue({ code: 'custom', message: 'Invalid bound endpoint' });
50
+ }
51
+ });
52
+ const queues = new Map();
53
+ const swept = new Set();
54
+ /** Canonical unpadded base64url of exactly 32 bytes: the accountId syntax. */
55
+ function isAccountId(value) {
56
+ if (typeof value !== 'string' || !/^[A-Za-z0-9_-]{43}$/.test(value))
57
+ return false;
58
+ const raw = Buffer.from(value, 'base64url');
59
+ return raw.length === 32 && raw.toString('base64url') === value;
60
+ }
61
+ function identityFile(dataDir) {
62
+ return resolve(join(dataDir, 'security', FILE_NAME));
63
+ }
64
+ function errorCode(error) {
65
+ return typeof error === 'object' && error !== null && 'code' in error && typeof error.code === 'string' ? error.code : undefined;
66
+ }
67
+ /** Operations on one file run one at a time within this process. */
68
+ function serialized(file, run) {
69
+ const previous = queues.get(file) ?? Promise.resolve();
70
+ const next = previous.then(run);
71
+ const settled = next.then(() => undefined, () => undefined);
72
+ queues.set(file, settled);
73
+ void settled.then(() => { if (queues.get(file) === settled)
74
+ queues.delete(file); });
75
+ return next;
76
+ }
77
+ /** Once per process: removes `<file>.<uuid>.tmp` left by a crash mid-write. Runs inside the file's queue, so no own write is in flight. */
78
+ async function sweepTemporaries(file) {
79
+ if (swept.has(file))
80
+ return;
81
+ swept.add(file);
82
+ const prefix = basename(file);
83
+ let names;
84
+ try {
85
+ names = await readdir(dirname(file));
86
+ }
87
+ catch {
88
+ return;
89
+ }
90
+ for (const name of names) {
91
+ if (name.indexOf(prefix) === 0 && TEMPORARY.test(name.slice(prefix.length)))
92
+ await rm(join(dirname(file), name), { force: true }).catch(() => undefined);
93
+ }
94
+ }
95
+ /** null when absent; every other read failure is key_unavailable. */
96
+ async function readText(file) {
97
+ let handle;
98
+ try {
99
+ handle = await open(file, 'r');
100
+ }
101
+ catch (error) {
102
+ if (errorCode(error) === 'ENOENT')
103
+ return null;
104
+ throw new BridgeSecurityError('key_unavailable');
105
+ }
106
+ try {
107
+ const info = await handle.stat();
108
+ if (!info.isFile() || info.size > MAX_FILE_BYTES)
109
+ throw new Error('Unexpected identity file');
110
+ return await handle.readFile('utf8');
111
+ }
112
+ catch {
113
+ throw new BridgeSecurityError('key_unavailable');
114
+ }
115
+ finally {
116
+ await handle.close();
117
+ }
118
+ }
119
+ function parseRecord(text) {
120
+ let value;
121
+ try {
122
+ value = JSON.parse(text);
123
+ }
124
+ catch {
125
+ throw new BridgeSecurityError('key_unavailable');
126
+ }
127
+ const parsed = recordSchema.safeParse(value);
128
+ if (!parsed.success)
129
+ throw new BridgeSecurityError('key_unavailable');
130
+ return parsed.data;
131
+ }
132
+ async function readRecord(file) {
133
+ const text = await readText(file);
134
+ return text === null ? null : parseRecord(text);
135
+ }
136
+ async function syncDirectory(directory) {
137
+ let handle;
138
+ try {
139
+ handle = await open(directory, 'r');
140
+ }
141
+ catch {
142
+ return;
143
+ }
144
+ try {
145
+ await handle.sync();
146
+ }
147
+ catch { /* directory fsync is unsupported on some platforms */ }
148
+ finally {
149
+ await handle.close();
150
+ }
151
+ }
152
+ /**
153
+ * Writes a complete 0600 file next to the target, then publishes it atomically: `link` when `exclusive` (never replaces an existing
154
+ * identity; returns false if one appeared meanwhile), else `rename` (replace). Throws storage_unavailable on any failure.
155
+ */
156
+ async function publish(file, record, exclusive) {
157
+ const directory = dirname(file);
158
+ const temporary = `${file}.${randomUUID()}.tmp`;
159
+ try {
160
+ await mkdir(directory, { recursive: true, mode: 0o700 });
161
+ const handle = await open(temporary, 'wx', 0o600);
162
+ try {
163
+ await handle.writeFile(JSON.stringify(recordSchema.parse(record)));
164
+ await handle.sync();
165
+ }
166
+ finally {
167
+ await handle.close();
168
+ }
169
+ if (exclusive) {
170
+ try {
171
+ await link(temporary, file);
172
+ }
173
+ catch (error) {
174
+ if (errorCode(error) === 'EEXIST')
175
+ return false;
176
+ throw error;
177
+ }
178
+ }
179
+ else {
180
+ await rename(temporary, file);
181
+ }
182
+ }
183
+ catch {
184
+ throw new BridgeSecurityError('storage_unavailable');
185
+ }
186
+ finally {
187
+ await rm(temporary, { force: true }).catch(() => undefined);
188
+ }
189
+ await syncDirectory(directory);
190
+ return true;
191
+ }
192
+ function sameBytes(left, right) {
193
+ if (left.length !== right.length)
194
+ return false;
195
+ let difference = 0;
196
+ for (let index = 0; index < left.length; index += 1)
197
+ difference |= left[index] ^ right[index];
198
+ return difference === 0;
199
+ }
200
+ async function generateRecord() {
201
+ try {
202
+ const endpoint = await crypto.subtle.generateKey(ECDH, true, ['deriveBits']);
203
+ const auth = await generateAuthKey(true);
204
+ return recordSchema.parse({
205
+ version: 2,
206
+ privateKey: await crypto.subtle.exportKey('jwk', endpoint.privateKey),
207
+ endpointPublicKey: await exportPublicKey(endpoint.publicKey),
208
+ authPublicKey: auth.publicKey,
209
+ authPrivateKey: await crypto.subtle.exportKey('jwk', auth.privateKey),
210
+ });
211
+ }
212
+ catch {
213
+ throw new BridgeSecurityError('key_unavailable');
214
+ }
215
+ }
216
+ /** Imports both keys non-extractable and proves each private key belongs to its pinned public point. */
217
+ async function importRecord(record) {
218
+ try {
219
+ const endpointPublic = await importPublicKey(record.endpointPublicKey);
220
+ const endpointJwk = await crypto.subtle.exportKey('jwk', endpointPublic);
221
+ if (record.privateKey.x !== endpointJwk.x || record.privateKey.y !== endpointJwk.y)
222
+ throw new Error('Endpoint key mismatch');
223
+ const endpointPrivate = await crypto.subtle.importKey('jwk', record.privateKey, ECDH, false, ['deriveBits']);
224
+ // WebCrypto may trust the supplied public coordinates of a JWK: prove the private scalar by a fresh exchange.
225
+ const probe = await crypto.subtle.generateKey(ECDH, false, ['deriveBits']);
226
+ const left = new Uint8Array(await crypto.subtle.deriveBits({ name: 'ECDH', public: probe.publicKey }, endpointPrivate, 256));
227
+ const right = new Uint8Array(await crypto.subtle.deriveBits({ name: 'ECDH', public: endpointPublic }, probe.privateKey, 256));
228
+ if (!sameBytes(left, right))
229
+ throw new Error('Endpoint key mismatch');
230
+ const authRaw = Buffer.from(record.authPublicKey, 'base64url');
231
+ if (authRaw.length !== 65 || authRaw[0] !== 4 || authRaw.toString('base64url') !== record.authPublicKey)
232
+ throw new Error('Auth key');
233
+ const authPublic = await crypto.subtle.importKey('raw', new Uint8Array(authRaw), ECDSA, true, ['verify']);
234
+ const authJwk = await crypto.subtle.exportKey('jwk', authPublic);
235
+ if (record.authPrivateKey.x !== authJwk.x || record.authPrivateKey.y !== authJwk.y)
236
+ throw new Error('Auth key mismatch');
237
+ const authPrivateKey = await crypto.subtle.importKey('jwk', record.authPrivateKey, ECDSA, false, ['sign']);
238
+ const challenge = crypto.getRandomValues(new Uint8Array(32));
239
+ const signature = await crypto.subtle.sign(SIGN, authPrivateKey, challenge);
240
+ if (!await crypto.subtle.verify(SIGN, authPublic, signature, challenge))
241
+ throw new Error('Auth key mismatch');
242
+ const endpointRaw = new Uint8Array(Buffer.from(record.endpointPublicKey, 'base64url'));
243
+ const endpointId = Buffer.from(await crypto.subtle.digest('SHA-256', endpointRaw)).toString('base64url');
244
+ const endpoint = record.endpoint === undefined ? null : { ...record.endpoint };
245
+ if (endpoint !== null && endpoint.id !== endpointId)
246
+ throw new Error('Endpoint id mismatch');
247
+ return {
248
+ keyPair: { publicKey: endpointPublic, privateKey: endpointPrivate }, endpointPublicKey: record.endpointPublicKey, endpointId,
249
+ authPublicKey: record.authPublicKey, authPrivateKey, endpoint, accountId: endpoint === null ? null : endpoint.ownerId,
250
+ };
251
+ }
252
+ catch {
253
+ throw new BridgeSecurityError('key_unavailable');
254
+ }
255
+ }
256
+ /** Reports the v2 identity file's state without importing keys. Throws BridgeSecurityError('key_unavailable') for a corrupted file. */
257
+ export async function inspectIdentityFile(dataDir) {
258
+ const record = await readRecord(identityFile(dataDir));
259
+ if (record === null)
260
+ return { state: 'absent', accountId: null };
261
+ return { state: 'ready', accountId: record.endpoint === undefined ? null : record.endpoint.ownerId };
262
+ }
263
+ /**
264
+ * True iff the data dir holds v1 ACCOUNT state: `refresh-token`, `security/account-enrollment.json`,
265
+ * `security/pending-approval-status.json` or `security/pending-approval-confirmation.json` exists, or `security/bridge-identity.json`
266
+ * has a readable endpoint owner that is not `solo:` (Solo's identity is never reported). An unreadable bridge-identity.json is not
267
+ * reported: its owner cannot be told apart from Solo's. Reads only; never modifies anything.
268
+ */
269
+ export function hasLegacyAccountState(dataDir) {
270
+ if (LEGACY_ACCOUNT_FILES.some(parts => existsSync(join(dataDir, ...parts))))
271
+ return true;
272
+ let value;
273
+ try {
274
+ value = JSON.parse(readFileSync(join(dataDir, 'security', 'bridge-identity.json'), 'utf8'));
275
+ }
276
+ catch {
277
+ return false;
278
+ }
279
+ if (typeof value !== 'object' || value === null || !('endpoint' in value))
280
+ return false;
281
+ const endpoint = value.endpoint;
282
+ if (typeof endpoint !== 'object' || endpoint === null || !('ownerId' in endpoint) || typeof endpoint.ownerId !== 'string')
283
+ return false;
284
+ return endpoint.ownerId.length > 0 && endpoint.ownerId.indexOf('solo:') !== 0;
285
+ }
286
+ /** Loads the identity without creating one: null when absent. Throws BridgeSecurityError('key_unavailable') for a corrupted file. */
287
+ export function readLedgerIdentity(dataDir) {
288
+ const file = identityFile(dataDir);
289
+ return serialized(file, async () => {
290
+ await sweepTemporaries(file);
291
+ const record = await readRecord(file);
292
+ return record === null ? null : importRecord(record);
293
+ });
294
+ }
295
+ /**
296
+ * Loads the identity, creating fresh keys only when the file is absent. Throws BridgeSecurityError: key_unavailable for an unreadable
297
+ * or corrupted file (never replaced), storage_unavailable when the new file cannot be written.
298
+ */
299
+ export function loadOrCreateLedgerIdentity(dataDir) {
300
+ const file = identityFile(dataDir);
301
+ return serialized(file, async () => {
302
+ await sweepTemporaries(file);
303
+ const existing = await readRecord(file);
304
+ if (existing !== null)
305
+ return importRecord(existing);
306
+ const created = await generateRecord();
307
+ if (await publish(file, created, true))
308
+ return importRecord(created);
309
+ // Another process created it between the read and the link: use theirs.
310
+ const raced = await readRecord(file);
311
+ if (raced === null)
312
+ throw new BridgeSecurityError('key_unavailable');
313
+ return importRecord(raced);
314
+ });
315
+ }
316
+ /**
317
+ * Binds the identity to `accountId` once: sets endpoint {version:1, id, ownerId: accountId, kind:'bridge', publicKey}. Idempotent for
318
+ * the same account. Throws BridgeSecurityError: wrong_context for a different (or malformed) accountId, key_unavailable when there is
319
+ * no valid identity to bind, storage_unavailable when the file cannot be written. Binds are serialized within this process only: the
320
+ * bridge's single-instance guarantee keeps a second process from binding concurrently.
321
+ */
322
+ export function bindLedgerIdentity(dataDir, accountId) {
323
+ if (!isAccountId(accountId))
324
+ return Promise.reject(new BridgeSecurityError('wrong_context'));
325
+ const file = identityFile(dataDir);
326
+ return serialized(file, async () => {
327
+ await sweepTemporaries(file);
328
+ const record = await readRecord(file);
329
+ if (record === null)
330
+ throw new BridgeSecurityError('key_unavailable');
331
+ const identity = await importRecord(record);
332
+ if (identity.accountId !== null) {
333
+ if (identity.accountId !== accountId)
334
+ throw new BridgeSecurityError('wrong_context');
335
+ return identity;
336
+ }
337
+ const endpoint = { version: 1, id: identity.endpointId, ownerId: accountId, kind: 'bridge', publicKey: identity.endpointPublicKey };
338
+ await publish(file, { ...record, endpoint }, false);
339
+ return { ...identity, endpoint: { ...endpoint }, accountId };
340
+ });
341
+ }
342
+ //# sourceMappingURL=ledger-identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger-identity.js","sourceRoot":"","sources":["../../../src/security/bridge/ledger-identity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import type { Ledger, LedgerGenesis, LedgerTransition, Signed } from '../ledger/protocol.js';
2
+ /** Record JSON per ledger_chain page (as the review and preview chunks): with the envelope it stays far below one relay frame. */
3
+ export declare const LEDGER_CHAIN_PAGE_BYTES = 98304;
4
+ /** The largest `limit`, and its default: every record of the largest chain. */
5
+ export declare const LEDGER_CHAIN_MAX_LIMIT: number;
6
+ export interface LedgerHeadAnswer {
7
+ accountId: string;
8
+ head: string;
9
+ revision: number;
10
+ }
11
+ export interface LedgerChainQuery {
12
+ fromRevision: number;
13
+ limit: number;
14
+ }
15
+ export interface LedgerChainPage extends LedgerHeadAnswer {
16
+ fromRevision: number;
17
+ records: Array<Signed<LedgerGenesis> | Signed<LedgerTransition>>;
18
+ more: boolean;
19
+ }
20
+ /** The stored chain and its tip (BridgeSecurityService.memberLedger). */
21
+ export interface HeldLedger {
22
+ ledger: Ledger;
23
+ accountId: string;
24
+ head: string;
25
+ revision: number;
26
+ }
27
+ /** ledger_head: exactly { bridgeId } naming this bridge. */
28
+ export declare function parseLedgerHeadQuery(payload: unknown, bridgeId: string): boolean;
29
+ /** ledger_chain: { bridgeId, fromRevision?, limit? } naming this bridge, nothing else; null when malformed. */
30
+ export declare function parseLedgerChainQuery(payload: unknown, bridgeId: string): LedgerChainQuery | null;
31
+ export declare function ledgerHeadAnswer(held: LedgerHeadAnswer): LedgerHeadAnswer;
32
+ /** The page of `held` that starts at query.fromRevision (empty, more false, past the tip). Never splits a record; always makes progress.
33
+ * `held` is only read; the page carries copies of its records. */
34
+ export declare function ledgerChainPage(held: HeldLedger, query: LedgerChainQuery, maxBytes?: number): LedgerChainPage;
@@ -0,0 +1,58 @@
1
+ /*
2
+ * The two read-only ledger RPCs a trusted browser sends over the encrypted command channel (CONTRACT §9, §11): `ledger_head` (this bridge's
3
+ * account, head and revision) and `ledger_chain` (its stored chain, in pages that fit one encrypted relay frame). Pure: request parsing and
4
+ * paging only. command-rpc.ts authorizes the caller (BridgeSecurityService.memberLedger) and answers.
5
+ *
6
+ * Wire (the usual command envelope; route { kind: 'command', command: <type>, requestId, sessionId: null }):
7
+ * ledger_head payload { bridgeId } -> data { accountId, head, revision }
8
+ * ledger_chain payload { bridgeId, fromRevision?: 0, limit?: 1025 } -> data { accountId, head, revision, fromRevision, records, more }
9
+ * records[i] is the record at revision fromRevision + i (revision 0 is the signed genesis, revision n is transitions[n - 1]), each exactly as
10
+ * stored ({ payload, signature }). A page holds at most `limit` records and, past its first record, at most LEDGER_CHAIN_PAGE_BYTES of
11
+ * record JSON; `more` says records after the page exist. head/revision are the stored tip when the page was cut.
12
+ */
13
+ import { LEDGER_LIMITS } from '../ledger/protocol.js';
14
+ /** Record JSON per ledger_chain page (as the review and preview chunks): with the envelope it stays far below one relay frame. */
15
+ export const LEDGER_CHAIN_PAGE_BYTES = 98_304;
16
+ /** The largest `limit`, and its default: every record of the largest chain. */
17
+ export const LEDGER_CHAIN_MAX_LIMIT = LEDGER_LIMITS.maxTransitions + 1;
18
+ function isPayload(value) {
19
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
20
+ }
21
+ function isCount(value, min, max) {
22
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= min && value <= max;
23
+ }
24
+ /** ledger_head: exactly { bridgeId } naming this bridge. */
25
+ export function parseLedgerHeadQuery(payload, bridgeId) {
26
+ return isPayload(payload) && Object.keys(payload).length === 1 && payload.bridgeId === bridgeId;
27
+ }
28
+ /** ledger_chain: { bridgeId, fromRevision?, limit? } naming this bridge, nothing else; null when malformed. */
29
+ export function parseLedgerChainQuery(payload, bridgeId) {
30
+ if (!isPayload(payload) || payload.bridgeId !== bridgeId)
31
+ return null;
32
+ if (Object.keys(payload).some(key => key !== 'bridgeId' && key !== 'fromRevision' && key !== 'limit'))
33
+ return null;
34
+ const fromRevision = payload.fromRevision === undefined ? 0 : payload.fromRevision;
35
+ const limit = payload.limit === undefined ? LEDGER_CHAIN_MAX_LIMIT : payload.limit;
36
+ if (!isCount(fromRevision, 0, LEDGER_LIMITS.maxTransitions) || !isCount(limit, 1, LEDGER_CHAIN_MAX_LIMIT))
37
+ return null;
38
+ return { fromRevision, limit };
39
+ }
40
+ export function ledgerHeadAnswer(held) {
41
+ return { accountId: held.accountId, head: held.head, revision: held.revision };
42
+ }
43
+ /** The page of `held` that starts at query.fromRevision (empty, more false, past the tip). Never splits a record; always makes progress.
44
+ * `held` is only read; the page carries copies of its records. */
45
+ export function ledgerChainPage(held, query, maxBytes = LEDGER_CHAIN_PAGE_BYTES) {
46
+ const all = [held.ledger.genesis, ...held.ledger.transitions];
47
+ const records = [];
48
+ let bytes = 0;
49
+ for (let revision = query.fromRevision; revision < all.length && records.length < query.limit; revision++) {
50
+ const size = Buffer.byteLength(JSON.stringify(all[revision]), 'utf8');
51
+ if (records.length > 0 && bytes + size > maxBytes)
52
+ break;
53
+ records.push(structuredClone(all[revision]));
54
+ bytes += size;
55
+ }
56
+ return { ...ledgerHeadAnswer(held), fromRevision: query.fromRevision, records, more: query.fromRevision + records.length < all.length };
57
+ }
58
+ //# sourceMappingURL=ledger-rpc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger-rpc.js","sourceRoot":"","sources":["../../../src/security/bridge/ledger-rpc.ts"],"names":[],"mappings":"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"}