@cogcoin/client 0.5.15 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/README.md +80 -25
  2. package/dist/app-paths.d.ts +5 -6
  3. package/dist/app-paths.js +8 -16
  4. package/dist/art/balance.txt +10 -0
  5. package/dist/art/welcome.txt +16 -0
  6. package/dist/bitcoind/bootstrap/controller.d.ts +1 -0
  7. package/dist/bitcoind/bootstrap/controller.js +53 -1
  8. package/dist/bitcoind/client/follow-block-times.d.ts +1 -0
  9. package/dist/bitcoind/client/follow-block-times.js +1 -1
  10. package/dist/bitcoind/client/internal-types.d.ts +7 -3
  11. package/dist/bitcoind/client/managed-client.d.ts +4 -2
  12. package/dist/bitcoind/client/managed-client.js +14 -0
  13. package/dist/bitcoind/client/sync-engine.js +72 -11
  14. package/dist/bitcoind/hash-order.d.ts +4 -0
  15. package/dist/bitcoind/hash-order.js +13 -0
  16. package/dist/bitcoind/indexer-daemon-main.js +11 -3
  17. package/dist/bitcoind/normalize.js +3 -2
  18. package/dist/bitcoind/processing-start-height.d.ts +5 -0
  19. package/dist/bitcoind/processing-start-height.js +7 -0
  20. package/dist/bitcoind/progress/constants.d.ts +4 -0
  21. package/dist/bitcoind/progress/constants.js +4 -0
  22. package/dist/bitcoind/progress/controller.d.ts +2 -1
  23. package/dist/bitcoind/progress/controller.js +3 -3
  24. package/dist/bitcoind/progress/follow-scene.d.ts +6 -2
  25. package/dist/bitcoind/progress/follow-scene.js +29 -6
  26. package/dist/bitcoind/progress/formatting.d.ts +1 -0
  27. package/dist/bitcoind/progress/formatting.js +6 -0
  28. package/dist/bitcoind/progress/train-scene.js +37 -18
  29. package/dist/bitcoind/progress/tty-renderer.d.ts +6 -1
  30. package/dist/bitcoind/progress/tty-renderer.js +8 -4
  31. package/dist/bitcoind/rpc.d.ts +2 -1
  32. package/dist/bitcoind/rpc.js +3 -0
  33. package/dist/bitcoind/types.d.ts +6 -0
  34. package/dist/bytes.d.ts +1 -0
  35. package/dist/bytes.js +3 -0
  36. package/dist/cli/art.d.ts +2 -0
  37. package/dist/cli/art.js +37 -0
  38. package/dist/cli/commands/client-admin.d.ts +2 -0
  39. package/dist/cli/commands/client-admin.js +91 -0
  40. package/dist/cli/commands/follow.js +0 -2
  41. package/dist/cli/commands/mining-admin.js +6 -47
  42. package/dist/cli/commands/mining-read.js +11 -50
  43. package/dist/cli/commands/mining-runtime.js +38 -3
  44. package/dist/cli/commands/service-runtime.js +0 -2
  45. package/dist/cli/commands/status.js +8 -2
  46. package/dist/cli/commands/sync.js +51 -4
  47. package/dist/cli/commands/wallet-admin.js +142 -136
  48. package/dist/cli/commands/wallet-mutation.js +91 -79
  49. package/dist/cli/commands/wallet-read.js +15 -18
  50. package/dist/cli/context.js +4 -14
  51. package/dist/cli/mining-format.d.ts +0 -1
  52. package/dist/cli/mining-format.js +5 -37
  53. package/dist/cli/mining-json.d.ts +0 -18
  54. package/dist/cli/mining-json.js +0 -35
  55. package/dist/cli/mutation-command-groups.d.ts +1 -2
  56. package/dist/cli/mutation-command-groups.js +0 -5
  57. package/dist/cli/mutation-json.d.ts +24 -145
  58. package/dist/cli/mutation-json.js +30 -136
  59. package/dist/cli/mutation-resolved-json.d.ts +0 -7
  60. package/dist/cli/mutation-resolved-json.js +4 -10
  61. package/dist/cli/mutation-success.d.ts +2 -0
  62. package/dist/cli/mutation-success.js +11 -1
  63. package/dist/cli/mutation-text-format.js +1 -3
  64. package/dist/cli/output.d.ts +1 -1
  65. package/dist/cli/output.js +254 -231
  66. package/dist/cli/parse.d.ts +1 -1
  67. package/dist/cli/parse.js +93 -122
  68. package/dist/cli/preview-json.d.ts +17 -120
  69. package/dist/cli/preview-json.js +14 -97
  70. package/dist/cli/prompt.js +8 -13
  71. package/dist/cli/read-json.d.ts +15 -37
  72. package/dist/cli/read-json.js +44 -140
  73. package/dist/cli/runner.js +10 -13
  74. package/dist/cli/types.d.ts +8 -17
  75. package/dist/cli/types.js +0 -2
  76. package/dist/cli/wallet-format.d.ts +1 -0
  77. package/dist/cli/wallet-format.js +205 -144
  78. package/dist/cli/workflow-hints.d.ts +3 -3
  79. package/dist/cli/workflow-hints.js +11 -8
  80. package/dist/client/default-client.d.ts +3 -1
  81. package/dist/client/default-client.js +45 -2
  82. package/dist/client/factory.js +1 -1
  83. package/dist/client/initialization.js +23 -0
  84. package/dist/client/persistence.js +5 -5
  85. package/dist/client/store-adapter.js +1 -0
  86. package/dist/sqlite/checkpoints.d.ts +1 -0
  87. package/dist/sqlite/checkpoints.js +7 -0
  88. package/dist/sqlite/store.js +14 -1
  89. package/dist/types.d.ts +1 -0
  90. package/dist/wallet/coin-control.d.ts +41 -12
  91. package/dist/wallet/coin-control.js +100 -428
  92. package/dist/wallet/descriptor-normalization.d.ts +1 -3
  93. package/dist/wallet/descriptor-normalization.js +0 -16
  94. package/dist/wallet/lifecycle.d.ts +7 -99
  95. package/dist/wallet/lifecycle.js +513 -968
  96. package/dist/wallet/managed-core-wallet.d.ts +13 -0
  97. package/dist/wallet/managed-core-wallet.js +20 -0
  98. package/dist/wallet/mining/constants.d.ts +5 -12
  99. package/dist/wallet/mining/constants.js +5 -12
  100. package/dist/wallet/mining/control.d.ts +1 -13
  101. package/dist/wallet/mining/control.js +45 -349
  102. package/dist/wallet/mining/index.d.ts +3 -4
  103. package/dist/wallet/mining/index.js +1 -2
  104. package/dist/wallet/mining/runner.d.ts +116 -13
  105. package/dist/wallet/mining/runner.js +885 -501
  106. package/dist/wallet/mining/runtime-artifacts.js +23 -3
  107. package/dist/wallet/mining/sentence-protocol.d.ts +44 -0
  108. package/dist/wallet/mining/sentence-protocol.js +123 -0
  109. package/dist/wallet/mining/sentences.d.ts +4 -8
  110. package/dist/wallet/mining/sentences.js +3 -52
  111. package/dist/wallet/mining/state.d.ts +11 -6
  112. package/dist/wallet/mining/state.js +7 -6
  113. package/dist/wallet/mining/types.d.ts +2 -30
  114. package/dist/wallet/mining/visualizer.d.ts +31 -3
  115. package/dist/wallet/mining/visualizer.js +135 -13
  116. package/dist/wallet/read/context.d.ts +0 -2
  117. package/dist/wallet/read/context.js +119 -140
  118. package/dist/wallet/read/filter.js +2 -11
  119. package/dist/wallet/read/index.d.ts +1 -1
  120. package/dist/wallet/read/project.js +24 -77
  121. package/dist/wallet/read/types.d.ts +10 -25
  122. package/dist/wallet/reset.d.ts +0 -1
  123. package/dist/wallet/reset.js +60 -138
  124. package/dist/wallet/root-resolution.d.ts +1 -5
  125. package/dist/wallet/root-resolution.js +0 -18
  126. package/dist/wallet/runtime.d.ts +0 -6
  127. package/dist/wallet/runtime.js +0 -8
  128. package/dist/wallet/state/client-password-agent.js +208 -0
  129. package/dist/wallet/state/client-password.d.ts +65 -0
  130. package/dist/wallet/state/client-password.js +952 -0
  131. package/dist/wallet/state/crypto.d.ts +1 -20
  132. package/dist/wallet/state/crypto.js +0 -63
  133. package/dist/wallet/state/provider.d.ts +23 -11
  134. package/dist/wallet/state/provider.js +248 -290
  135. package/dist/wallet/state/storage.d.ts +2 -2
  136. package/dist/wallet/state/storage.js +48 -16
  137. package/dist/wallet/tx/anchor.d.ts +3 -28
  138. package/dist/wallet/tx/anchor.js +349 -1250
  139. package/dist/wallet/tx/bitcoin-transfer.d.ts +35 -0
  140. package/dist/wallet/tx/bitcoin-transfer.js +200 -0
  141. package/dist/wallet/tx/cog.d.ts +5 -1
  142. package/dist/wallet/tx/cog.js +149 -185
  143. package/dist/wallet/tx/common.d.ts +61 -8
  144. package/dist/wallet/tx/common.js +266 -146
  145. package/dist/wallet/tx/domain-admin.d.ts +3 -1
  146. package/dist/wallet/tx/domain-admin.js +61 -99
  147. package/dist/wallet/tx/domain-market.d.ts +5 -1
  148. package/dist/wallet/tx/domain-market.js +221 -228
  149. package/dist/wallet/tx/field.d.ts +4 -10
  150. package/dist/wallet/tx/field.js +83 -924
  151. package/dist/wallet/tx/identity-selector.d.ts +9 -3
  152. package/dist/wallet/tx/identity-selector.js +17 -35
  153. package/dist/wallet/tx/index.d.ts +3 -1
  154. package/dist/wallet/tx/index.js +2 -1
  155. package/dist/wallet/tx/register.d.ts +3 -1
  156. package/dist/wallet/tx/register.js +62 -220
  157. package/dist/wallet/tx/reputation.d.ts +3 -1
  158. package/dist/wallet/tx/reputation.js +58 -95
  159. package/dist/wallet/types.d.ts +8 -122
  160. package/package.json +5 -5
  161. package/dist/wallet/archive.d.ts +0 -4
  162. package/dist/wallet/archive.js +0 -41
  163. package/dist/wallet/mining/hook-protocol.d.ts +0 -47
  164. package/dist/wallet/mining/hook-protocol.js +0 -161
  165. package/dist/wallet/mining/hook-runner.js +0 -52
  166. package/dist/wallet/mining/hooks.d.ts +0 -38
  167. package/dist/wallet/mining/hooks.js +0 -520
  168. package/dist/wallet/state/explicit-lock.d.ts +0 -4
  169. package/dist/wallet/state/explicit-lock.js +0 -19
  170. package/dist/wallet/state/session.d.ts +0 -12
  171. package/dist/wallet/state/session.js +0 -23
  172. /package/dist/wallet/{mining/hook-runner.d.ts → state/client-password-agent.d.ts} +0 -0
@@ -1,16 +1,11 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
- import { execFile, spawn } from "node:child_process";
3
- import { access, constants, mkdir, readFile, rm } from "node:fs/promises";
4
- import { dirname, join } from "node:path";
2
+ import { execFile } from "node:child_process";
3
+ import { join } from "node:path";
5
4
  import { promisify } from "node:util";
6
5
  import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
7
- import { writeFileAtomic } from "../fs/atomic.js";
6
+ import { createLegacyKeychainServiceName, deleteClientProtectedSecret, ensureClientPasswordConfigured as ensureClientPasswordConfiguredWithLocalFile, inspectClientPasswordReadiness, loadClientProtectedSecret, lockClientPasswordSession, readClientPasswordSessionStatus, storeClientProtectedSecret, changeClientPassword as changeClientPasswordWithLocalFile, unlockClientPasswordSession as unlockClientPasswordSessionWithLocalFile, } from "./client-password.js";
8
7
  const execFileAsync = promisify(execFile);
9
- const KEYCHAIN_SERVICE_NAME = "org.cogcoin.wallet";
10
- const LINUX_SECRET_TOOL_ATTRIBUTE_APPLICATION = "application";
11
- const LINUX_SECRET_TOOL_ATTRIBUTE_KIND = "secret-kind";
12
- const LINUX_SECRET_TOOL_ATTRIBUTE_KEY_ID = "key-id";
13
- const LINUX_SECRET_TOOL_SECRET_KIND = "wallet-secret";
8
+ const KEYCHAIN_SERVICE_NAME = createLegacyKeychainServiceName();
14
9
  export function createWalletSecretReference(walletRootId) {
15
10
  return {
16
11
  kind: "wallet-state-key",
@@ -29,86 +24,17 @@ function bytesToBase64(secret) {
29
24
  function base64ToBytes(secret) {
30
25
  return new Uint8Array(Buffer.from(secret, "base64"));
31
26
  }
32
- function createLinuxSecretToolAttributes(keyId) {
33
- return [
34
- LINUX_SECRET_TOOL_ATTRIBUTE_APPLICATION,
35
- KEYCHAIN_SERVICE_NAME,
36
- LINUX_SECRET_TOOL_ATTRIBUTE_KIND,
37
- LINUX_SECRET_TOOL_SECRET_KIND,
38
- LINUX_SECRET_TOOL_ATTRIBUTE_KEY_ID,
39
- keyId,
40
- ];
41
- }
42
- function createLinuxSecretToolError(message, cause) {
43
- return cause === undefined ? new Error(message) : new Error(message, { cause });
44
- }
45
- function isWalletSecretProviderMessage(error, message) {
46
- return error instanceof Error && error.message === message;
47
- }
48
- function sanitizeSecretKeyId(keyId) {
49
- return keyId.replace(/[^a-zA-Z0-9._-]+/g, "-");
50
- }
51
27
  function resolveSecretDirectoryPath(options) {
52
28
  return join(options.stateRoot ?? resolveWalletRuntimePathsForTesting().stateRoot, "secrets");
53
29
  }
54
- function isLinuxSecretServiceUnavailableMessage(stderr) {
55
- const normalized = stderr.trim().toLowerCase();
56
- if (normalized.length === 0) {
57
- return false;
30
+ function resolveRuntimeRootPath(options) {
31
+ if (options.runtimeRoot != null) {
32
+ return options.runtimeRoot;
58
33
  }
59
- return normalized.includes("secret service")
60
- || normalized.includes("org.freedesktop.secrets")
61
- || normalized.includes("dbus")
62
- || normalized.includes("d-bus")
63
- || normalized.includes("cannot autolaunch")
64
- || normalized.includes("no such secret collection")
65
- || normalized.includes("collection is locked")
66
- || normalized.includes("prompt dismissed")
67
- || normalized.includes("not available");
68
- }
69
- function isLinuxSecretToolMissingSecretMessage(stderr) {
70
- const normalized = stderr.trim().toLowerCase();
71
- if (normalized.length === 0) {
72
- return true;
34
+ if (options.stateRoot != null) {
35
+ return join(options.stateRoot, ".client-runtime");
73
36
  }
74
- return normalized.includes("no matching")
75
- || normalized.includes("not found")
76
- || normalized.includes("does not exist")
77
- || normalized.includes("no such item")
78
- || normalized.includes("no secret");
79
- }
80
- async function runLinuxSecretTool(args, options = {}) {
81
- return await new Promise((resolve, reject) => {
82
- const child = spawn("secret-tool", [...args], {
83
- stdio: ["pipe", "pipe", "pipe"],
84
- });
85
- let stdout = "";
86
- let stderr = "";
87
- child.once("error", reject);
88
- child.stdout.setEncoding("utf8");
89
- child.stderr.setEncoding("utf8");
90
- child.stdout.on("data", (chunk) => {
91
- stdout += chunk;
92
- });
93
- child.stderr.on("data", (chunk) => {
94
- stderr += chunk;
95
- });
96
- child.once("close", (exitCode, signal) => {
97
- resolve({
98
- stdout,
99
- stderr,
100
- exitCode,
101
- signal,
102
- });
103
- });
104
- child.stdin.on("error", (error) => {
105
- if ("code" in error && error.code === "EPIPE") {
106
- return;
107
- }
108
- reject(error);
109
- });
110
- child.stdin.end(options.stdin ?? undefined, "utf8");
111
- });
37
+ return options.runtimeRoot ?? resolveWalletRuntimePathsForTesting().runtimeRoot;
112
38
  }
113
39
  export class MemoryWalletSecretProvider {
114
40
  kind = "memory-test";
@@ -126,6 +52,39 @@ export class MemoryWalletSecretProvider {
126
52
  async deleteSecret(keyId) {
127
53
  this.#values.delete(keyId);
128
54
  }
55
+ withPrompter(_prompter) {
56
+ return this;
57
+ }
58
+ async inspectClientPasswordReadiness() {
59
+ return "ready";
60
+ }
61
+ async ensureClientPasswordConfigured(_prompter) {
62
+ return "already-configured";
63
+ }
64
+ async unlockClientPasswordSession(_prompter) {
65
+ return {
66
+ unlocked: true,
67
+ unlockUntilUnixMs: null,
68
+ };
69
+ }
70
+ async changeClientPassword(_prompter) {
71
+ return {
72
+ unlocked: true,
73
+ unlockUntilUnixMs: null,
74
+ };
75
+ }
76
+ async lockClientPasswordSession() {
77
+ return {
78
+ unlocked: false,
79
+ unlockUntilUnixMs: null,
80
+ };
81
+ }
82
+ async readClientPasswordSessionStatus() {
83
+ return {
84
+ unlocked: true,
85
+ unlockUntilUnixMs: null,
86
+ };
87
+ }
129
88
  }
130
89
  class MacOsKeychainWalletSecretProvider {
131
90
  kind = "macos-keychain";
@@ -167,234 +126,167 @@ class MacOsKeychainWalletSecretProvider {
167
126
  ]).catch(() => undefined);
168
127
  }
169
128
  }
170
- class LinuxSecretToolWalletSecretProvider {
171
- kind = "linux-secret-service";
172
- #runner;
173
- constructor(runner = runLinuxSecretTool) {
174
- this.#runner = runner;
175
- }
176
- async #invoke(options) {
177
- try {
178
- const result = await this.#runner(options.args, {
179
- stdin: options.stdin,
180
- });
181
- if (result.exitCode === 0) {
182
- return result;
183
- }
184
- if (isLinuxSecretServiceUnavailableMessage(result.stderr)) {
185
- throw createLinuxSecretToolError("wallet_secret_provider_linux_secret_service_unavailable");
186
- }
187
- if ((options.operation === "load" || options.ignoreMissing)
188
- && isLinuxSecretToolMissingSecretMessage(result.stderr)) {
189
- throw createLinuxSecretToolError(`wallet_secret_missing_${options.keyId}`);
190
- }
191
- throw createLinuxSecretToolError("wallet_secret_provider_linux_runtime_error");
192
- }
193
- catch (error) {
194
- if (error instanceof Error
195
- && (error.message === "wallet_secret_provider_linux_secret_tool_missing"
196
- || error.message === "wallet_secret_provider_linux_secret_service_unavailable"
197
- || error.message === "wallet_secret_provider_linux_runtime_error"
198
- || error.message === `wallet_secret_missing_${options.keyId}`)) {
199
- throw error;
200
- }
201
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
202
- throw createLinuxSecretToolError("wallet_secret_provider_linux_secret_tool_missing", error);
203
- }
204
- throw createLinuxSecretToolError("wallet_secret_provider_linux_runtime_error", error);
205
- }
129
+ class LocalFileWalletSecretProvider {
130
+ kind;
131
+ #stateRoot;
132
+ #directoryPath;
133
+ #platform;
134
+ #runtimeRoot;
135
+ #runtimeErrorCode;
136
+ #legacyMacKeychainReader;
137
+ #prompter;
138
+ constructor(options) {
139
+ this.kind = options.kind;
140
+ this.#stateRoot = options.stateRoot;
141
+ this.#directoryPath = options.directoryPath;
142
+ this.#platform = options.platform;
143
+ this.#runtimeRoot = options.runtimeRoot;
144
+ this.#runtimeErrorCode = options.runtimeErrorCode;
145
+ this.#legacyMacKeychainReader = options.legacyMacKeychainReader ?? null;
146
+ this.#prompter = options.prompter ?? null;
206
147
  }
207
148
  async loadSecret(keyId) {
208
- const result = await this.#invoke({
209
- args: ["lookup", ...createLinuxSecretToolAttributes(keyId)],
149
+ return await loadClientProtectedSecret({
150
+ platform: this.#platform,
151
+ stateRoot: this.#stateRoot,
152
+ runtimeRoot: this.#runtimeRoot,
153
+ directoryPath: this.#directoryPath,
154
+ runtimeErrorCode: this.#runtimeErrorCode,
155
+ legacyMacKeychainReader: this.#legacyMacKeychainReader,
210
156
  keyId,
211
- operation: "load",
157
+ prompt: this.#prompter ?? undefined,
212
158
  });
213
- return base64ToBytes(result.stdout.trim());
214
159
  }
215
160
  async storeSecret(keyId, secret) {
216
- await this.#invoke({
217
- args: [
218
- "store",
219
- "--label",
220
- `Cogcoin wallet secret (${keyId})`,
221
- ...createLinuxSecretToolAttributes(keyId),
222
- ],
161
+ await storeClientProtectedSecret({
162
+ platform: this.#platform,
163
+ stateRoot: this.#stateRoot,
164
+ runtimeRoot: this.#runtimeRoot,
165
+ directoryPath: this.#directoryPath,
166
+ runtimeErrorCode: this.#runtimeErrorCode,
167
+ legacyMacKeychainReader: this.#legacyMacKeychainReader,
223
168
  keyId,
224
- operation: "store",
225
- stdin: bytesToBase64(secret),
169
+ secret,
170
+ prompt: this.#prompter ?? undefined,
226
171
  });
227
172
  }
228
173
  async deleteSecret(keyId) {
229
- try {
230
- await this.#invoke({
231
- args: ["clear", ...createLinuxSecretToolAttributes(keyId)],
232
- keyId,
233
- operation: "delete",
234
- ignoreMissing: true,
235
- });
236
- }
237
- catch (error) {
238
- if (error instanceof Error && error.message === `wallet_secret_missing_${keyId}`) {
239
- return;
240
- }
241
- throw error;
242
- }
243
- }
244
- }
245
- class LinuxLocalFileWalletSecretProvider {
246
- kind = "linux-local-file";
247
- #directoryPath;
248
- constructor(directoryPath) {
249
- this.#directoryPath = directoryPath;
250
- }
251
- #resolveSecretPath(keyId) {
252
- return join(this.#directoryPath, `${sanitizeSecretKeyId(keyId)}.secret`);
253
- }
254
- async hasSecret(keyId) {
255
- try {
256
- await access(this.#resolveSecretPath(keyId), constants.F_OK);
257
- return true;
258
- }
259
- catch {
260
- return false;
261
- }
262
- }
263
- async loadSecret(keyId) {
264
- try {
265
- const encoded = await readFile(this.#resolveSecretPath(keyId), "utf8");
266
- return base64ToBytes(encoded.trim());
267
- }
268
- catch (error) {
269
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
270
- throw new Error(`wallet_secret_missing_${keyId}`);
271
- }
272
- throw createLinuxSecretToolError("wallet_secret_provider_linux_runtime_error", error);
273
- }
274
- }
275
- async storeSecret(keyId, secret) {
276
- try {
277
- await mkdir(this.#directoryPath, { recursive: true, mode: 0o700 });
278
- await writeFileAtomic(this.#resolveSecretPath(keyId), `${bytesToBase64(secret)}\n`, { mode: 0o600 });
279
- }
280
- catch (error) {
281
- throw createLinuxSecretToolError("wallet_secret_provider_linux_runtime_error", error);
282
- }
283
- }
284
- async deleteSecret(keyId) {
285
- await rm(this.#resolveSecretPath(keyId), { force: true }).catch(() => undefined);
286
- }
287
- }
288
- class LinuxFallbackWalletSecretProvider {
289
- kind = "linux-secret-service-fallback";
290
- #secretService;
291
- #fileProvider;
292
- constructor(options) {
293
- this.#secretService = options.secretService;
294
- this.#fileProvider = options.fileProvider;
295
- }
296
- async #loadFromFileOrRethrow(keyId, error) {
297
- try {
298
- return await this.#fileProvider.loadSecret(keyId);
299
- }
300
- catch (fileError) {
301
- if (isWalletSecretProviderMessage(fileError, `wallet_secret_missing_${keyId}`)
302
- && (error.message === "wallet_secret_provider_linux_secret_tool_missing"
303
- || error.message === "wallet_secret_provider_linux_secret_service_unavailable")) {
304
- throw error;
305
- }
306
- throw fileError;
307
- }
308
- }
309
- async loadSecret(keyId) {
310
- try {
311
- return await this.#secretService.loadSecret(keyId);
312
- }
313
- catch (error) {
314
- if (!(error instanceof Error)) {
315
- throw error;
316
- }
317
- if (error.message === "wallet_secret_provider_linux_secret_tool_missing"
318
- || error.message === "wallet_secret_provider_linux_secret_service_unavailable"
319
- || error.message === `wallet_secret_missing_${keyId}`) {
320
- return await this.#loadFromFileOrRethrow(keyId, error);
321
- }
322
- if (error.message === "wallet_secret_provider_linux_runtime_error"
323
- && await this.#fileProvider.hasSecret(keyId)) {
324
- return await this.#fileProvider.loadSecret(keyId);
325
- }
326
- throw error;
327
- }
328
- }
329
- async storeSecret(keyId, secret) {
330
- try {
331
- await this.#secretService.storeSecret(keyId, secret);
332
- }
333
- catch (error) {
334
- if (isWalletSecretProviderMessage(error, "wallet_secret_provider_linux_secret_tool_missing")
335
- || isWalletSecretProviderMessage(error, "wallet_secret_provider_linux_secret_service_unavailable")) {
336
- await this.#fileProvider.storeSecret(keyId, secret);
337
- return;
338
- }
339
- throw error;
340
- }
174
+ await deleteClientProtectedSecret({
175
+ platform: this.#platform,
176
+ stateRoot: this.#stateRoot,
177
+ runtimeRoot: this.#runtimeRoot,
178
+ directoryPath: this.#directoryPath,
179
+ runtimeErrorCode: this.#runtimeErrorCode,
180
+ keyId,
181
+ });
341
182
  }
342
- async deleteSecret(keyId) {
343
- await Promise.allSettled([
344
- this.#secretService.deleteSecret(keyId),
345
- this.#fileProvider.deleteSecret(keyId),
346
- ]);
183
+ withPrompter(prompter) {
184
+ return new LocalFileWalletSecretProvider({
185
+ stateRoot: this.#stateRoot,
186
+ directoryPath: this.#directoryPath,
187
+ kind: this.kind,
188
+ platform: this.#platform,
189
+ runtimeRoot: this.#runtimeRoot,
190
+ runtimeErrorCode: this.#runtimeErrorCode,
191
+ legacyMacKeychainReader: this.#legacyMacKeychainReader,
192
+ prompter,
193
+ });
347
194
  }
348
- }
349
- class WindowsDpapiWalletSecretProvider {
350
- kind = "windows-dpapi";
351
- #directoryPath;
352
- constructor(directoryPath) {
353
- this.#directoryPath = directoryPath;
195
+ async inspectClientPasswordReadiness() {
196
+ return await inspectClientPasswordReadiness({
197
+ platform: this.#platform,
198
+ stateRoot: this.#stateRoot,
199
+ runtimeRoot: this.#runtimeRoot,
200
+ directoryPath: this.#directoryPath,
201
+ runtimeErrorCode: this.#runtimeErrorCode,
202
+ legacyMacKeychainReader: this.#legacyMacKeychainReader,
203
+ });
354
204
  }
355
- #resolveSecretPath(keyId) {
356
- const fileId = keyId.replace(/[^a-zA-Z0-9._-]+/g, "-");
357
- return join(this.#directoryPath, `${fileId}.dpapi`);
205
+ async ensureClientPasswordConfigured(prompter) {
206
+ const result = await ensureClientPasswordConfiguredWithLocalFile({
207
+ platform: this.#platform,
208
+ stateRoot: this.#stateRoot,
209
+ runtimeRoot: this.#runtimeRoot,
210
+ directoryPath: this.#directoryPath,
211
+ runtimeErrorCode: this.#runtimeErrorCode,
212
+ legacyMacKeychainReader: this.#legacyMacKeychainReader,
213
+ prompt: prompter,
214
+ });
215
+ return result.action;
216
+ }
217
+ async unlockClientPasswordSession(prompter) {
218
+ return await unlockClientPasswordSessionWithLocalFile({
219
+ platform: this.#platform,
220
+ stateRoot: this.#stateRoot,
221
+ runtimeRoot: this.#runtimeRoot,
222
+ directoryPath: this.#directoryPath,
223
+ runtimeErrorCode: this.#runtimeErrorCode,
224
+ legacyMacKeychainReader: this.#legacyMacKeychainReader,
225
+ prompt: prompter,
226
+ });
358
227
  }
359
- async loadSecret(keyId) {
360
- const secretPath = this.#resolveSecretPath(keyId);
361
- const encoded = await readFile(secretPath, "utf8");
362
- const { stdout } = await execFileAsync("powershell.exe", [
363
- "-NoProfile",
364
- "-NonInteractive",
365
- "-Command",
366
- "$data=[Convert]::FromBase64String($args[0]);$value=[System.Security.Cryptography.ProtectedData]::Unprotect($data,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser);[Console]::Out.Write([Convert]::ToBase64String($value));",
367
- encoded.trim(),
368
- ]);
369
- return base64ToBytes(stdout.trim());
228
+ async changeClientPassword(prompter) {
229
+ return await changeClientPasswordWithLocalFile({
230
+ platform: this.#platform,
231
+ stateRoot: this.#stateRoot,
232
+ runtimeRoot: this.#runtimeRoot,
233
+ directoryPath: this.#directoryPath,
234
+ runtimeErrorCode: this.#runtimeErrorCode,
235
+ legacyMacKeychainReader: this.#legacyMacKeychainReader,
236
+ prompt: prompter,
237
+ });
370
238
  }
371
- async storeSecret(keyId, secret) {
372
- const secretPath = this.#resolveSecretPath(keyId);
373
- await mkdir(dirname(secretPath), { recursive: true });
374
- const { stdout } = await execFileAsync("powershell.exe", [
375
- "-NoProfile",
376
- "-NonInteractive",
377
- "-Command",
378
- "$data=[Convert]::FromBase64String($args[0]);$value=[System.Security.Cryptography.ProtectedData]::Protect($data,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser);[Console]::Out.Write([Convert]::ToBase64String($value));",
379
- bytesToBase64(secret),
380
- ]);
381
- await writeFileAtomic(secretPath, `${stdout.trim()}\n`, { mode: 0o600 });
239
+ async lockClientPasswordSession() {
240
+ return await lockClientPasswordSession({
241
+ platform: this.#platform,
242
+ stateRoot: this.#stateRoot,
243
+ runtimeRoot: this.#runtimeRoot,
244
+ directoryPath: this.#directoryPath,
245
+ runtimeErrorCode: this.#runtimeErrorCode,
246
+ legacyMacKeychainReader: this.#legacyMacKeychainReader,
247
+ });
382
248
  }
383
- async deleteSecret(keyId) {
384
- await rm(this.#resolveSecretPath(keyId), { force: true }).catch(() => undefined);
249
+ async readClientPasswordSessionStatus() {
250
+ return await readClientPasswordSessionStatus({
251
+ platform: this.#platform,
252
+ stateRoot: this.#stateRoot,
253
+ runtimeRoot: this.#runtimeRoot,
254
+ directoryPath: this.#directoryPath,
255
+ runtimeErrorCode: this.#runtimeErrorCode,
256
+ legacyMacKeychainReader: this.#legacyMacKeychainReader,
257
+ });
385
258
  }
386
259
  }
387
260
  function createWalletSecretProviderForPlatform(platform, options = {}) {
388
- if (platform === "darwin") {
389
- return new MacOsKeychainWalletSecretProvider();
390
- }
391
261
  if (platform === "win32") {
392
- return new WindowsDpapiWalletSecretProvider(resolveSecretDirectoryPath(options));
262
+ return new LocalFileWalletSecretProvider({
263
+ stateRoot: options.stateRoot ?? resolveWalletRuntimePathsForTesting().stateRoot,
264
+ directoryPath: resolveSecretDirectoryPath(options),
265
+ kind: "windows-local-file",
266
+ platform,
267
+ runtimeRoot: resolveRuntimeRootPath(options),
268
+ runtimeErrorCode: "wallet_secret_provider_windows_runtime_error",
269
+ });
393
270
  }
394
271
  if (platform === "linux") {
395
- return new LinuxFallbackWalletSecretProvider({
396
- secretService: new LinuxSecretToolWalletSecretProvider(options.linuxSecretToolRunner),
397
- fileProvider: new LinuxLocalFileWalletSecretProvider(resolveSecretDirectoryPath(options)),
272
+ return new LocalFileWalletSecretProvider({
273
+ stateRoot: options.stateRoot ?? resolveWalletRuntimePathsForTesting().stateRoot,
274
+ directoryPath: resolveSecretDirectoryPath(options),
275
+ kind: "linux-local-file",
276
+ platform,
277
+ runtimeRoot: resolveRuntimeRootPath(options),
278
+ runtimeErrorCode: "wallet_secret_provider_linux_runtime_error",
279
+ });
280
+ }
281
+ if (platform === "darwin") {
282
+ return new LocalFileWalletSecretProvider({
283
+ stateRoot: options.stateRoot ?? resolveWalletRuntimePathsForTesting().stateRoot,
284
+ directoryPath: resolveSecretDirectoryPath(options),
285
+ kind: "macos-local-file",
286
+ platform,
287
+ runtimeRoot: resolveRuntimeRootPath(options),
288
+ runtimeErrorCode: "wallet_secret_provider_macos_runtime_error",
289
+ legacyMacKeychainReader: new MacOsKeychainWalletSecretProvider(),
398
290
  });
399
291
  }
400
292
  throw new Error(`wallet_secret_provider_unsupported_${platform}`);
@@ -424,11 +316,77 @@ export function createLazyDefaultWalletSecretProvider() {
424
316
  async deleteSecret(keyId) {
425
317
  await getResolved().deleteSecret(keyId);
426
318
  },
319
+ withPrompter(prompter) {
320
+ return getResolved().withPrompter?.(prompter) ?? getResolved();
321
+ },
322
+ async inspectClientPasswordReadiness() {
323
+ return await getResolved().inspectClientPasswordReadiness?.() ?? "ready";
324
+ },
325
+ async ensureClientPasswordConfigured(prompter) {
326
+ return await getResolved().ensureClientPasswordConfigured?.(prompter) ?? "already-configured";
327
+ },
328
+ async unlockClientPasswordSession(prompter) {
329
+ return await getResolved().unlockClientPasswordSession?.(prompter) ?? {
330
+ unlocked: true,
331
+ unlockUntilUnixMs: null,
332
+ };
333
+ },
334
+ async changeClientPassword(prompter) {
335
+ return await getResolved().changeClientPassword?.(prompter) ?? {
336
+ unlocked: true,
337
+ unlockUntilUnixMs: null,
338
+ };
339
+ },
340
+ async lockClientPasswordSession() {
341
+ return await getResolved().lockClientPasswordSession?.() ?? {
342
+ unlocked: false,
343
+ unlockUntilUnixMs: null,
344
+ };
345
+ },
346
+ async readClientPasswordSessionStatus() {
347
+ return await getResolved().readClientPasswordSessionStatus?.() ?? {
348
+ unlocked: true,
349
+ unlockUntilUnixMs: null,
350
+ };
351
+ },
427
352
  };
428
353
  }
429
354
  export function createMemoryWalletSecretProviderForTesting() {
430
355
  return new MemoryWalletSecretProvider();
431
356
  }
357
+ export function withInteractiveWalletSecretProvider(provider, prompter) {
358
+ return provider.withPrompter?.(prompter) ?? provider;
359
+ }
360
+ export async function ensureClientPasswordConfigured(provider, prompter) {
361
+ return await provider.ensureClientPasswordConfigured?.(prompter) ?? "already-configured";
362
+ }
363
+ export async function inspectClientPasswordSetupReadiness(provider) {
364
+ return await provider.inspectClientPasswordReadiness?.() ?? "ready";
365
+ }
366
+ export async function unlockClientPassword(provider, prompter) {
367
+ return await provider.unlockClientPasswordSession?.(prompter) ?? {
368
+ unlocked: true,
369
+ unlockUntilUnixMs: null,
370
+ };
371
+ }
372
+ export async function changeClientPassword(provider, prompter) {
373
+ return await provider.changeClientPassword?.(prompter) ?? {
374
+ unlocked: true,
375
+ unlockUntilUnixMs: null,
376
+ };
377
+ }
378
+ export async function lockClientPassword(provider) {
379
+ return await provider.lockClientPasswordSession?.() ?? {
380
+ unlocked: false,
381
+ unlockUntilUnixMs: null,
382
+ };
383
+ }
384
+ export async function readClientPasswordStatus(provider) {
385
+ return await provider.readClientPasswordSessionStatus?.() ?? {
386
+ unlocked: true,
387
+ unlockUntilUnixMs: null,
388
+ };
389
+ }
432
390
  export function createWalletRootId() {
433
391
  return `wallet-${randomUUID().replaceAll("-", "")}`;
434
392
  }
@@ -12,11 +12,11 @@ export interface RawWalletStateEnvelope {
12
12
  source: "primary" | "backup";
13
13
  envelope: EncryptedEnvelopeV1;
14
14
  }
15
- export type WalletStateSaveAccess = Uint8Array | string | {
15
+ export type WalletStateSaveAccess = {
16
16
  provider: WalletSecretProvider;
17
17
  secretReference: WalletSecretReference;
18
18
  };
19
- export type WalletStateLoadAccess = Uint8Array | string | {
19
+ export type WalletStateLoadAccess = {
20
20
  provider: WalletSecretProvider;
21
21
  };
22
22
  export declare function loadRawWalletStateEnvelope(paths: WalletStateStoragePaths): Promise<RawWalletStateEnvelope | null>;