@cogcoin/client 1.1.6 → 1.1.8

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 (125) hide show
  1. package/README.md +2 -2
  2. package/dist/bitcoind/indexer-daemon.js +29 -79
  3. package/dist/bitcoind/managed-runtime/bitcoind-runtime.d.ts +20 -0
  4. package/dist/bitcoind/managed-runtime/bitcoind-runtime.js +74 -0
  5. package/dist/bitcoind/managed-runtime/bitcoind-status.d.ts +11 -0
  6. package/dist/bitcoind/managed-runtime/bitcoind-status.js +44 -0
  7. package/dist/bitcoind/managed-runtime/indexer-runtime.d.ts +15 -0
  8. package/dist/bitcoind/managed-runtime/indexer-runtime.js +82 -0
  9. package/dist/bitcoind/managed-runtime/types.d.ts +40 -0
  10. package/dist/bitcoind/node.d.ts +2 -2
  11. package/dist/bitcoind/node.js +2 -2
  12. package/dist/bitcoind/rpc.d.ts +2 -1
  13. package/dist/bitcoind/rpc.js +53 -3
  14. package/dist/bitcoind/service.js +47 -127
  15. package/dist/cli/command-registry.d.ts +1 -1
  16. package/dist/cli/command-registry.js +2 -64
  17. package/dist/cli/commands/client-admin.js +3 -18
  18. package/dist/cli/commands/mining-runtime.js +4 -60
  19. package/dist/cli/commands/wallet-admin.js +6 -6
  20. package/dist/cli/context.js +1 -3
  21. package/dist/cli/mining-json.d.ts +1 -22
  22. package/dist/cli/mining-json.js +0 -23
  23. package/dist/cli/output.js +16 -2
  24. package/dist/cli/parse.js +0 -2
  25. package/dist/cli/preview-json.d.ts +1 -22
  26. package/dist/cli/preview-json.js +0 -19
  27. package/dist/cli/types.d.ts +1 -3
  28. package/dist/cli/wallet-format.js +1 -1
  29. package/dist/cli/workflow-hints.d.ts +1 -2
  30. package/dist/cli/workflow-hints.js +5 -8
  31. package/dist/wallet/lifecycle/context.js +0 -1
  32. package/dist/wallet/lifecycle/repair-mining.d.ts +1 -5
  33. package/dist/wallet/lifecycle/repair-mining.js +5 -39
  34. package/dist/wallet/lifecycle/repair.js +0 -3
  35. package/dist/wallet/lifecycle/setup.js +10 -8
  36. package/dist/wallet/lifecycle/types.d.ts +1 -4
  37. package/dist/wallet/managed-core-wallet.d.ts +2 -0
  38. package/dist/wallet/managed-core-wallet.js +27 -1
  39. package/dist/wallet/mining/candidate.d.ts +1 -0
  40. package/dist/wallet/mining/candidate.js +38 -6
  41. package/dist/wallet/mining/competitiveness.d.ts +1 -0
  42. package/dist/wallet/mining/competitiveness.js +6 -0
  43. package/dist/wallet/mining/cycle.d.ts +2 -0
  44. package/dist/wallet/mining/cycle.js +14 -4
  45. package/dist/wallet/mining/engine-state.js +10 -0
  46. package/dist/wallet/mining/engine-types.d.ts +1 -0
  47. package/dist/wallet/mining/index.d.ts +1 -1
  48. package/dist/wallet/mining/index.js +1 -1
  49. package/dist/wallet/mining/publish.d.ts +3 -0
  50. package/dist/wallet/mining/publish.js +78 -6
  51. package/dist/wallet/mining/runner.d.ts +0 -32
  52. package/dist/wallet/mining/runner.js +59 -104
  53. package/dist/wallet/mining/stop.d.ts +7 -0
  54. package/dist/wallet/mining/stop.js +23 -0
  55. package/dist/wallet/mining/supervisor.d.ts +2 -36
  56. package/dist/wallet/mining/supervisor.js +139 -246
  57. package/dist/wallet/mining/visualizer-sync.js +79 -15
  58. package/dist/wallet/read/context.d.ts +1 -5
  59. package/dist/wallet/read/context.js +21 -205
  60. package/dist/wallet/read/managed-services.d.ts +33 -0
  61. package/dist/wallet/read/managed-services.js +222 -0
  62. package/dist/wallet/reset/artifacts.d.ts +16 -0
  63. package/dist/wallet/reset/artifacts.js +141 -0
  64. package/dist/wallet/reset/execution.d.ts +38 -0
  65. package/dist/wallet/reset/execution.js +458 -0
  66. package/dist/wallet/reset/preflight.d.ts +7 -0
  67. package/dist/wallet/reset/preflight.js +116 -0
  68. package/dist/wallet/reset/preview.d.ts +2 -0
  69. package/dist/wallet/reset/preview.js +50 -0
  70. package/dist/wallet/reset/process-cleanup.d.ts +12 -0
  71. package/dist/wallet/reset/process-cleanup.js +179 -0
  72. package/dist/wallet/reset/types.d.ts +189 -0
  73. package/dist/wallet/reset/types.js +1 -0
  74. package/dist/wallet/reset.d.ts +4 -119
  75. package/dist/wallet/reset.js +4 -882
  76. package/dist/wallet/state/client-password/bootstrap.d.ts +2 -0
  77. package/dist/wallet/state/client-password/bootstrap.js +3 -0
  78. package/dist/wallet/state/client-password/context.d.ts +10 -0
  79. package/dist/wallet/state/client-password/context.js +46 -0
  80. package/dist/wallet/state/client-password/crypto.d.ts +34 -0
  81. package/dist/wallet/state/client-password/crypto.js +117 -0
  82. package/dist/wallet/state/client-password/files.d.ts +10 -0
  83. package/dist/wallet/state/client-password/files.js +109 -0
  84. package/dist/wallet/state/client-password/legacy-cleanup.d.ts +11 -0
  85. package/dist/wallet/state/client-password/legacy-cleanup.js +338 -0
  86. package/dist/wallet/state/client-password/messages.d.ts +3 -0
  87. package/dist/wallet/state/client-password/messages.js +9 -0
  88. package/dist/wallet/state/client-password/migration.d.ts +4 -0
  89. package/dist/wallet/state/client-password/migration.js +32 -0
  90. package/dist/wallet/state/client-password/prompts.d.ts +12 -0
  91. package/dist/wallet/state/client-password/prompts.js +79 -0
  92. package/dist/wallet/state/client-password/protected-secrets.d.ts +13 -0
  93. package/dist/wallet/state/client-password/protected-secrets.js +90 -0
  94. package/dist/wallet/state/client-password/readiness.d.ts +4 -0
  95. package/dist/wallet/state/client-password/readiness.js +48 -0
  96. package/dist/wallet/state/client-password/references.d.ts +1 -0
  97. package/dist/wallet/state/client-password/references.js +56 -0
  98. package/dist/wallet/state/client-password/rotation.d.ts +6 -0
  99. package/dist/wallet/state/client-password/rotation.js +98 -0
  100. package/dist/wallet/state/client-password/session-policy.d.ts +6 -0
  101. package/dist/wallet/state/client-password/session-policy.js +28 -0
  102. package/dist/wallet/state/client-password/session.d.ts +19 -0
  103. package/dist/wallet/state/client-password/session.js +170 -0
  104. package/dist/wallet/state/client-password/setup.d.ts +8 -0
  105. package/dist/wallet/state/client-password/setup.js +49 -0
  106. package/dist/wallet/state/client-password/types.d.ts +82 -0
  107. package/dist/wallet/state/client-password/types.js +5 -0
  108. package/dist/wallet/state/client-password.d.ts +7 -38
  109. package/dist/wallet/state/client-password.js +52 -937
  110. package/dist/wallet/tx/anchor.js +123 -216
  111. package/dist/wallet/tx/cog.js +294 -489
  112. package/dist/wallet/tx/common.d.ts +2 -0
  113. package/dist/wallet/tx/common.js +2 -0
  114. package/dist/wallet/tx/domain-admin.js +111 -220
  115. package/dist/wallet/tx/domain-market.js +401 -681
  116. package/dist/wallet/tx/executor.d.ts +176 -0
  117. package/dist/wallet/tx/executor.js +302 -0
  118. package/dist/wallet/tx/field.js +109 -215
  119. package/dist/wallet/tx/register.js +158 -269
  120. package/dist/wallet/tx/reputation.js +120 -227
  121. package/package.json +1 -1
  122. package/dist/wallet/mining/worker-main.d.ts +0 -1
  123. package/dist/wallet/mining/worker-main.js +0 -17
  124. package/dist/wallet/state/client-password-agent.d.ts +0 -1
  125. package/dist/wallet/state/client-password-agent.js +0 -211
@@ -0,0 +1,3 @@
1
+ export declare function describeClientPasswordLockedMessage(): string;
2
+ export declare function describeClientPasswordSetupMessage(): string;
3
+ export declare function describeClientPasswordMigrationMessage(): string;
@@ -0,0 +1,9 @@
1
+ export function describeClientPasswordLockedMessage() {
2
+ return "Wallet state exists but the client password is locked.";
3
+ }
4
+ export function describeClientPasswordSetupMessage() {
5
+ return "Wallet-local secret access is not configured yet. Run `cogcoin init` to create the client password.";
6
+ }
7
+ export function describeClientPasswordMigrationMessage() {
8
+ return "Wallet-local secret migration is still required. Run `cogcoin init` to migrate this client to password-protected local secrets.";
9
+ }
@@ -0,0 +1,4 @@
1
+ import type { ClientPasswordResolvedContext } from "./types.js";
2
+ export declare function migrateReferencedSecrets(options: ClientPasswordResolvedContext & {
3
+ derivedKey: Uint8Array;
4
+ }): Promise<boolean>;
@@ -0,0 +1,32 @@
1
+ import { resolveLocalSecretFilePath } from "./context.js";
2
+ import { createWrappedSecretEnvelope } from "./crypto.js";
3
+ import { readLocalSecretFile, writeWrappedSecretEnvelope } from "./files.js";
4
+ import { collectReferencedSecretIds } from "./references.js";
5
+ import { legacyMacKeychainHasSecret } from "./readiness.js";
6
+ export async function migrateReferencedSecrets(options) {
7
+ const keyIds = await collectReferencedSecretIds(options.stateRoot);
8
+ let migrated = false;
9
+ for (const keyId of keyIds) {
10
+ const localPath = resolveLocalSecretFilePath(options.directoryPath, keyId);
11
+ const localState = await readLocalSecretFile(localPath);
12
+ if (localState.state === "wrapped") {
13
+ continue;
14
+ }
15
+ if (localState.state === "raw") {
16
+ await writeWrappedSecretEnvelope(localPath, createWrappedSecretEnvelope(localState.secret, options.derivedKey));
17
+ migrated = true;
18
+ continue;
19
+ }
20
+ if (await legacyMacKeychainHasSecret(options, keyId)) {
21
+ try {
22
+ const secret = await options.legacyMacKeychainReader.loadSecret(keyId);
23
+ await writeWrappedSecretEnvelope(localPath, createWrappedSecretEnvelope(secret, options.derivedKey));
24
+ migrated = true;
25
+ }
26
+ catch {
27
+ // Best-effort legacy migration only.
28
+ }
29
+ }
30
+ }
31
+ return migrated;
32
+ }
@@ -0,0 +1,12 @@
1
+ import type { ClientPasswordPrompt, ClientPasswordResolvedContext } from "./types.js";
2
+ export declare function promptForHiddenValue(prompt: ClientPasswordPrompt, message: string): Promise<string>;
3
+ export declare function promptForVerifiedClientPassword(options: {
4
+ context: ClientPasswordResolvedContext;
5
+ prompt: ClientPasswordPrompt;
6
+ promptMessage: string;
7
+ ttyErrorCode: string;
8
+ }): Promise<Buffer>;
9
+ export declare function promptForNewPassword(prompt: ClientPasswordPrompt): Promise<{
10
+ passwordBytes: Buffer;
11
+ passwordHint: string;
12
+ }>;
@@ -0,0 +1,79 @@
1
+ import { loadClientPasswordStateOrNull } from "./files.js";
2
+ import { verifyPassword, zeroizeBuffer, } from "./crypto.js";
3
+ import { describeReadinessError, inspectClientPasswordReadinessResolved } from "./readiness.js";
4
+ export async function promptForHiddenValue(prompt, message) {
5
+ const value = prompt.promptHidden != null
6
+ ? await prompt.promptHidden(message)
7
+ : await prompt.prompt(message);
8
+ return value.trim();
9
+ }
10
+ export async function promptForVerifiedClientPassword(options) {
11
+ const readiness = await inspectClientPasswordReadinessResolved(options.context);
12
+ if (readiness !== "ready") {
13
+ throw new Error(describeReadinessError(readiness));
14
+ }
15
+ if (!options.prompt.isInteractive) {
16
+ throw new Error(options.ttyErrorCode);
17
+ }
18
+ const state = await loadClientPasswordStateOrNull(options.context.passwordStatePath);
19
+ if (state === null) {
20
+ throw new Error("wallet_client_password_setup_required");
21
+ }
22
+ let attempts = 0;
23
+ while (true) {
24
+ if (attempts >= 2 && state.passwordHint.trim().length > 0) {
25
+ options.prompt.writeLine(`Hint: ${state.passwordHint}`);
26
+ }
27
+ const passwordText = await promptForHiddenValue(options.prompt, options.promptMessage);
28
+ const passwordBytes = Buffer.from(passwordText, "utf8");
29
+ let derivedKey = null;
30
+ try {
31
+ derivedKey = await verifyPassword({
32
+ state,
33
+ passwordBytes,
34
+ });
35
+ }
36
+ finally {
37
+ zeroizeBuffer(passwordBytes);
38
+ }
39
+ if (derivedKey !== null) {
40
+ return derivedKey;
41
+ }
42
+ attempts += 1;
43
+ options.prompt.writeLine("Incorrect client password.");
44
+ }
45
+ }
46
+ export async function promptForNewPassword(prompt) {
47
+ if (!prompt.isInteractive) {
48
+ throw new Error("wallet_client_password_setup_requires_tty");
49
+ }
50
+ while (true) {
51
+ const first = await promptForHiddenValue(prompt, "Create client password: ");
52
+ const firstBytes = Buffer.from(first, "utf8");
53
+ if (firstBytes.length === 0) {
54
+ zeroizeBuffer(firstBytes);
55
+ prompt.writeLine("Client password cannot be blank.");
56
+ continue;
57
+ }
58
+ const second = await promptForHiddenValue(prompt, "Confirm client password: ");
59
+ const secondBytes = Buffer.from(second, "utf8");
60
+ if (!firstBytes.equals(secondBytes)) {
61
+ zeroizeBuffer(firstBytes);
62
+ zeroizeBuffer(secondBytes);
63
+ prompt.writeLine("Client password entries did not match.");
64
+ continue;
65
+ }
66
+ zeroizeBuffer(secondBytes);
67
+ let passwordHint = "";
68
+ while (passwordHint.length === 0) {
69
+ passwordHint = (await prompt.prompt("Password hint: ")).trim();
70
+ if (passwordHint.length === 0) {
71
+ prompt.writeLine("Password hint cannot be blank.");
72
+ }
73
+ }
74
+ return {
75
+ passwordBytes: firstBytes,
76
+ passwordHint,
77
+ };
78
+ }
79
+ }
@@ -0,0 +1,13 @@
1
+ import type { ClientPasswordPrompt, ClientPasswordResolvedContext } from "./types.js";
2
+ export declare function loadClientProtectedSecretResolved(options: ClientPasswordResolvedContext & {
3
+ keyId: string;
4
+ prompt?: ClientPasswordPrompt;
5
+ }): Promise<Uint8Array>;
6
+ export declare function storeClientProtectedSecretResolved(options: ClientPasswordResolvedContext & {
7
+ keyId: string;
8
+ secret: Uint8Array;
9
+ prompt?: ClientPasswordPrompt;
10
+ }): Promise<void>;
11
+ export declare function deleteClientProtectedSecretResolved(options: ClientPasswordResolvedContext & {
12
+ keyId: string;
13
+ }): Promise<void>;
@@ -0,0 +1,90 @@
1
+ import { mkdir, rm } from "node:fs/promises";
2
+ import { createRuntimeError, resolveLocalSecretFilePath } from "./context.js";
3
+ import { loadClientPasswordStateOrNull, readLocalSecretFile, writeWrappedSecretEnvelope, } from "./files.js";
4
+ import { legacyMacKeychainHasSecret } from "./readiness.js";
5
+ import { finalizePendingClientPasswordRotationIfNeeded } from "./rotation.js";
6
+ import { decryptClientProtectedSecretWithSessionResolved, encryptClientProtectedSecretWithSessionResolved, unlockClientPasswordSessionResolved, } from "./session.js";
7
+ async function decryptWrappedSecretWithSessionResolved(options) {
8
+ let secret = decryptClientProtectedSecretWithSessionResolved(options, options.envelope);
9
+ if (secret === null && options.prompt != null && options.prompt.isInteractive) {
10
+ await unlockClientPasswordSessionResolved({
11
+ context: options,
12
+ prompt: options.prompt,
13
+ });
14
+ secret = decryptClientProtectedSecretWithSessionResolved(options, options.envelope);
15
+ }
16
+ if (secret === null) {
17
+ throw new Error("wallet_client_password_locked");
18
+ }
19
+ return secret;
20
+ }
21
+ async function encryptWrappedSecretWithSessionResolved(options) {
22
+ let envelope = encryptClientProtectedSecretWithSessionResolved(options, options.secret);
23
+ if (envelope === null && options.prompt != null && options.prompt.isInteractive) {
24
+ await unlockClientPasswordSessionResolved({
25
+ context: options,
26
+ prompt: options.prompt,
27
+ });
28
+ envelope = encryptClientProtectedSecretWithSessionResolved(options, options.secret);
29
+ }
30
+ if (envelope === null) {
31
+ throw new Error("wallet_client_password_locked");
32
+ }
33
+ return envelope;
34
+ }
35
+ export async function loadClientProtectedSecretResolved(options) {
36
+ try {
37
+ await finalizePendingClientPasswordRotationIfNeeded(options);
38
+ const passwordState = await loadClientPasswordStateOrNull(options.passwordStatePath);
39
+ const localState = await readLocalSecretFile(resolveLocalSecretFilePath(options.directoryPath, options.keyId));
40
+ if (passwordState === null) {
41
+ if (localState.state === "raw" || await legacyMacKeychainHasSecret(options, options.keyId)) {
42
+ throw new Error("wallet_client_password_migration_required");
43
+ }
44
+ throw new Error("wallet_client_password_setup_required");
45
+ }
46
+ if (localState.state === "missing") {
47
+ if (await legacyMacKeychainHasSecret(options, options.keyId)) {
48
+ throw new Error("wallet_client_password_migration_required");
49
+ }
50
+ throw new Error(`wallet_secret_missing_${options.keyId}`);
51
+ }
52
+ if (localState.state === "raw") {
53
+ throw new Error("wallet_client_password_migration_required");
54
+ }
55
+ return await decryptWrappedSecretWithSessionResolved({
56
+ ...options,
57
+ envelope: localState.envelope,
58
+ });
59
+ }
60
+ catch (error) {
61
+ const message = error instanceof Error ? error.message : String(error);
62
+ if (message.startsWith("wallet_client_password_")
63
+ || message.startsWith("wallet_secret_missing_")) {
64
+ throw error;
65
+ }
66
+ throw createRuntimeError(options.runtimeErrorCode, error);
67
+ }
68
+ }
69
+ export async function storeClientProtectedSecretResolved(options) {
70
+ try {
71
+ await finalizePendingClientPasswordRotationIfNeeded(options);
72
+ const passwordState = await loadClientPasswordStateOrNull(options.passwordStatePath);
73
+ if (passwordState === null) {
74
+ throw new Error("wallet_client_password_setup_required");
75
+ }
76
+ await mkdir(options.directoryPath, { recursive: true, mode: 0o700 });
77
+ const envelope = await encryptWrappedSecretWithSessionResolved(options);
78
+ await writeWrappedSecretEnvelope(resolveLocalSecretFilePath(options.directoryPath, options.keyId), envelope);
79
+ }
80
+ catch (error) {
81
+ const message = error instanceof Error ? error.message : String(error);
82
+ if (message.startsWith("wallet_client_password_")) {
83
+ throw error;
84
+ }
85
+ throw createRuntimeError(options.runtimeErrorCode, error);
86
+ }
87
+ }
88
+ export async function deleteClientProtectedSecretResolved(options) {
89
+ await rm(resolveLocalSecretFilePath(options.directoryPath, options.keyId), { force: true }).catch(() => undefined);
90
+ }
@@ -0,0 +1,4 @@
1
+ import type { ClientPasswordReadiness, ClientPasswordResolvedContext } from "./types.js";
2
+ export declare function legacyMacKeychainHasSecret(context: ClientPasswordResolvedContext, keyId: string): Promise<boolean>;
3
+ export declare function inspectClientPasswordReadinessResolved(context: ClientPasswordResolvedContext): Promise<ClientPasswordReadiness>;
4
+ export declare function describeReadinessError(readiness: ClientPasswordReadiness): string;
@@ -0,0 +1,48 @@
1
+ import { resolveLocalSecretFilePath } from "./context.js";
2
+ import { readLocalSecretFile, loadClientPasswordStateOrNull } from "./files.js";
3
+ import { collectReferencedSecretIds } from "./references.js";
4
+ export async function legacyMacKeychainHasSecret(context, keyId) {
5
+ if (context.platform !== "darwin" || context.legacyMacKeychainReader == null) {
6
+ return false;
7
+ }
8
+ try {
9
+ await context.legacyMacKeychainReader.loadSecret(keyId);
10
+ return true;
11
+ }
12
+ catch {
13
+ return false;
14
+ }
15
+ }
16
+ async function inspectReadinessForKey(context, keyId) {
17
+ const local = await readLocalSecretFile(resolveLocalSecretFilePath(context.directoryPath, keyId));
18
+ const keychain = await legacyMacKeychainHasSecret(context, keyId);
19
+ return { local, keychain };
20
+ }
21
+ export async function inspectClientPasswordReadinessResolved(context) {
22
+ const passwordState = await loadClientPasswordStateOrNull(context.passwordStatePath);
23
+ const keyIds = await collectReferencedSecretIds(context.stateRoot);
24
+ if (keyIds.length === 0) {
25
+ return passwordState === null ? "setup-required" : "ready";
26
+ }
27
+ for (const keyId of keyIds) {
28
+ const sourceState = await inspectReadinessForKey(context, keyId);
29
+ if (passwordState === null) {
30
+ if (sourceState.local.state === "raw" || sourceState.keychain) {
31
+ return "migration-required";
32
+ }
33
+ continue;
34
+ }
35
+ if (sourceState.local.state === "raw") {
36
+ return "migration-required";
37
+ }
38
+ if (sourceState.local.state === "missing" && sourceState.keychain) {
39
+ return "migration-required";
40
+ }
41
+ }
42
+ return passwordState === null ? "setup-required" : "ready";
43
+ }
44
+ export function describeReadinessError(readiness) {
45
+ return readiness === "migration-required"
46
+ ? "wallet_client_password_migration_required"
47
+ : "wallet_client_password_setup_required";
48
+ }
@@ -0,0 +1 @@
1
+ export declare function collectReferencedSecretIds(stateRoot: string): Promise<string[]>;
@@ -0,0 +1,56 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { isMissingFileError } from "./context.js";
4
+ async function collectWalletStateRoots(stateRoot) {
5
+ const roots = [stateRoot];
6
+ const seedsRoot = join(stateRoot, "seeds");
7
+ try {
8
+ const entries = await readdir(seedsRoot, { withFileTypes: true });
9
+ for (const entry of entries) {
10
+ if (entry.isDirectory()) {
11
+ roots.push(join(seedsRoot, entry.name));
12
+ }
13
+ }
14
+ }
15
+ catch (error) {
16
+ if (!isMissingFileError(error)) {
17
+ throw error;
18
+ }
19
+ }
20
+ return roots;
21
+ }
22
+ async function readReferencedSecretIdsFromWalletStateRoot(walletStateRoot) {
23
+ const ids = new Set();
24
+ const candidatePaths = [
25
+ join(walletStateRoot, "wallet-state.enc"),
26
+ join(walletStateRoot, "wallet-state.enc.bak"),
27
+ join(walletStateRoot, "wallet-init-pending.enc"),
28
+ join(walletStateRoot, "wallet-init-pending.enc.bak"),
29
+ ];
30
+ for (const candidatePath of candidatePaths) {
31
+ try {
32
+ const parsed = JSON.parse(await readFile(candidatePath, "utf8"));
33
+ const keyId = parsed.secretProvider?.keyId?.trim() ?? "";
34
+ if (keyId.length > 0) {
35
+ ids.add(keyId);
36
+ }
37
+ }
38
+ catch (error) {
39
+ if (!isMissingFileError(error)) {
40
+ continue;
41
+ }
42
+ }
43
+ }
44
+ return ids;
45
+ }
46
+ export async function collectReferencedSecretIds(stateRoot) {
47
+ const ids = new Set();
48
+ const roots = await collectWalletStateRoots(stateRoot);
49
+ for (const root of roots) {
50
+ const rootIds = await readReferencedSecretIdsFromWalletStateRoot(root);
51
+ for (const keyId of rootIds) {
52
+ ids.add(keyId);
53
+ }
54
+ }
55
+ return [...ids].sort((left, right) => left.localeCompare(right));
56
+ }
@@ -0,0 +1,6 @@
1
+ import type { ClientPasswordPrompt, ClientPasswordResolvedContext, ClientPasswordSessionStatus } from "./types.js";
2
+ export declare function finalizePendingClientPasswordRotationIfNeeded(context: ClientPasswordResolvedContext): Promise<void>;
3
+ export declare function changeClientPasswordResolved(options: {
4
+ context: ClientPasswordResolvedContext;
5
+ prompt: ClientPasswordPrompt;
6
+ }): Promise<ClientPasswordSessionStatus>;
@@ -0,0 +1,98 @@
1
+ import { mkdir, rm } from "node:fs/promises";
2
+ import { resolveLocalSecretFilePath } from "./context.js";
3
+ import { createClientPasswordState, createWrappedSecretEnvelope, decryptWrappedSecretEnvelope, zeroizeBuffer, } from "./crypto.js";
4
+ import { loadClientPasswordRotationJournalOrNull, readLocalSecretFile, writeClientPasswordRotationJournal, writeClientPasswordState, writeWrappedSecretEnvelope, } from "./files.js";
5
+ import { collectReferencedSecretIds } from "./references.js";
6
+ import { promptForNewPassword, promptForVerifiedClientPassword, } from "./prompts.js";
7
+ import { resolveClientPasswordPromptSessionPolicy, resolvePostChangeClientPasswordUnlockUntilUnixMs, } from "./session-policy.js";
8
+ import { readClientPasswordSessionStatusResolved, startClientPasswordSessionWithExpiryResolved, } from "./session.js";
9
+ export async function finalizePendingClientPasswordRotationIfNeeded(context) {
10
+ const journal = await loadClientPasswordRotationJournalOrNull(context.rotationJournalPath);
11
+ if (journal === null) {
12
+ return;
13
+ }
14
+ await mkdir(context.directoryPath, { recursive: true, mode: 0o700 });
15
+ for (const secretEntry of journal.secrets) {
16
+ await writeWrappedSecretEnvelope(resolveLocalSecretFilePath(context.directoryPath, secretEntry.keyId), secretEntry.envelope);
17
+ }
18
+ await writeClientPasswordState(context.passwordStatePath, journal.nextState);
19
+ await rm(context.rotationJournalPath, { force: true });
20
+ }
21
+ async function prepareClientPasswordRotation(options) {
22
+ const next = await createClientPasswordState({
23
+ passwordBytes: options.newPasswordBytes,
24
+ passwordHint: options.newPasswordHint,
25
+ });
26
+ const keyIds = await collectReferencedSecretIds(options.stateRoot);
27
+ const secrets = [];
28
+ try {
29
+ for (const keyId of keyIds) {
30
+ const localState = await readLocalSecretFile(resolveLocalSecretFilePath(options.directoryPath, keyId));
31
+ if (localState.state === "missing") {
32
+ throw new Error(`wallet_secret_missing_${keyId}`);
33
+ }
34
+ if (localState.state === "raw") {
35
+ throw new Error("wallet_client_password_migration_required");
36
+ }
37
+ const secret = decryptWrappedSecretEnvelope(localState.envelope, options.currentDerivedKey);
38
+ try {
39
+ secrets.push({
40
+ keyId,
41
+ envelope: createWrappedSecretEnvelope(secret, next.derivedKey),
42
+ });
43
+ }
44
+ finally {
45
+ zeroizeBuffer(secret);
46
+ }
47
+ }
48
+ return {
49
+ journal: {
50
+ format: "cogcoin-client-password-rotation",
51
+ version: 1,
52
+ nextState: next.state,
53
+ secrets,
54
+ },
55
+ newDerivedKey: next.derivedKey,
56
+ };
57
+ }
58
+ catch (error) {
59
+ zeroizeBuffer(next.derivedKey);
60
+ throw error;
61
+ }
62
+ }
63
+ export async function changeClientPasswordResolved(options) {
64
+ await finalizePendingClientPasswordRotationIfNeeded(options.context);
65
+ const previousSession = await readClientPasswordSessionStatusResolved(options.context);
66
+ const currentDerivedKey = await promptForVerifiedClientPassword({
67
+ context: options.context,
68
+ prompt: options.prompt,
69
+ promptMessage: "Current client password: ",
70
+ ttyErrorCode: "wallet_client_password_change_requires_tty",
71
+ });
72
+ const nextPassword = await promptForNewPassword(options.prompt);
73
+ let newDerivedKey = null;
74
+ try {
75
+ const prepared = await prepareClientPasswordRotation({
76
+ ...options.context,
77
+ currentDerivedKey,
78
+ newPasswordBytes: nextPassword.passwordBytes,
79
+ newPasswordHint: nextPassword.passwordHint,
80
+ });
81
+ newDerivedKey = prepared.newDerivedKey;
82
+ await mkdir(options.context.directoryPath, { recursive: true, mode: 0o700 });
83
+ await writeClientPasswordRotationJournal(options.context.rotationJournalPath, prepared.journal);
84
+ await finalizePendingClientPasswordRotationIfNeeded(options.context);
85
+ const session = await startClientPasswordSessionWithExpiryResolved({
86
+ ...options.context,
87
+ derivedKey: newDerivedKey,
88
+ unlockUntilUnixMs: resolvePostChangeClientPasswordUnlockUntilUnixMs(previousSession, resolveClientPasswordPromptSessionPolicy(options.prompt)),
89
+ });
90
+ newDerivedKey = null;
91
+ return session;
92
+ }
93
+ finally {
94
+ zeroizeBuffer(currentDerivedKey);
95
+ zeroizeBuffer(nextPassword.passwordBytes);
96
+ zeroizeBuffer(newDerivedKey);
97
+ }
98
+ }
@@ -0,0 +1,6 @@
1
+ import type { ClientPasswordPrompt, ClientPasswordSessionStatus } from "./types.js";
2
+ export type ClientPasswordSessionPolicy = "default-60m" | "init-24h" | "mining-indefinite";
3
+ export declare function bindClientPasswordPromptSessionPolicy<T extends ClientPasswordPrompt>(prompt: T, policy: ClientPasswordSessionPolicy): T;
4
+ export declare function resolveClientPasswordPromptSessionPolicy(prompt: ClientPasswordPrompt | null | undefined): ClientPasswordSessionPolicy;
5
+ export declare function resolveClientPasswordSessionUnlockUntilUnixMs(policy: ClientPasswordSessionPolicy, nowUnixMs?: number): number | null;
6
+ export declare function resolvePostChangeClientPasswordUnlockUntilUnixMs(status: ClientPasswordSessionStatus, policy: ClientPasswordSessionPolicy, nowUnixMs?: number): number | null;
@@ -0,0 +1,28 @@
1
+ import { CLIENT_PASSWORD_DEFAULT_UNLOCK_SECONDS, CLIENT_PASSWORD_SETUP_AUTO_UNLOCK_SECONDS } from "./crypto.js";
2
+ const promptPolicies = new WeakMap();
3
+ export function bindClientPasswordPromptSessionPolicy(prompt, policy) {
4
+ promptPolicies.set(prompt, policy);
5
+ return prompt;
6
+ }
7
+ export function resolveClientPasswordPromptSessionPolicy(prompt) {
8
+ if (prompt == null) {
9
+ return "default-60m";
10
+ }
11
+ return promptPolicies.get(prompt) ?? "default-60m";
12
+ }
13
+ export function resolveClientPasswordSessionUnlockUntilUnixMs(policy, nowUnixMs = Date.now()) {
14
+ switch (policy) {
15
+ case "default-60m":
16
+ return nowUnixMs + (CLIENT_PASSWORD_DEFAULT_UNLOCK_SECONDS * 1_000);
17
+ case "init-24h":
18
+ return nowUnixMs + (CLIENT_PASSWORD_SETUP_AUTO_UNLOCK_SECONDS * 1_000);
19
+ case "mining-indefinite":
20
+ return null;
21
+ }
22
+ }
23
+ export function resolvePostChangeClientPasswordUnlockUntilUnixMs(status, policy, nowUnixMs = Date.now()) {
24
+ if (status.unlocked) {
25
+ return status.unlockUntilUnixMs;
26
+ }
27
+ return resolveClientPasswordSessionUnlockUntilUnixMs(policy, nowUnixMs);
28
+ }
@@ -0,0 +1,19 @@
1
+ import { type ClientPasswordSessionPolicy } from "./session-policy.js";
2
+ import type { ClientPasswordPrompt, ClientPasswordResolvedContext, ClientPasswordSessionStatus, WrappedSecretEnvelopeV1 } from "./types.js";
3
+ export declare function destroyAllClientPasswordSessionsResolved(): void;
4
+ export declare function readClientPasswordSessionStatusResolved(context: ClientPasswordResolvedContext): Promise<ClientPasswordSessionStatus>;
5
+ export declare function lockClientPasswordSessionResolved(context: ClientPasswordResolvedContext): Promise<ClientPasswordSessionStatus>;
6
+ export declare function startClientPasswordSessionResolved(options: ClientPasswordResolvedContext & {
7
+ derivedKey: Buffer;
8
+ sessionPolicy: ClientPasswordSessionPolicy;
9
+ }): Promise<ClientPasswordSessionStatus>;
10
+ export declare function startClientPasswordSessionWithExpiryResolved(options: ClientPasswordResolvedContext & {
11
+ derivedKey: Buffer;
12
+ unlockUntilUnixMs: number | null;
13
+ }): Promise<ClientPasswordSessionStatus>;
14
+ export declare function unlockClientPasswordSessionResolved(options: {
15
+ context: ClientPasswordResolvedContext;
16
+ prompt: ClientPasswordPrompt;
17
+ }): Promise<ClientPasswordSessionStatus>;
18
+ export declare function decryptClientProtectedSecretWithSessionResolved(context: ClientPasswordResolvedContext, envelope: WrappedSecretEnvelopeV1): Uint8Array | null;
19
+ export declare function encryptClientProtectedSecretWithSessionResolved(context: ClientPasswordResolvedContext, secret: Uint8Array): WrappedSecretEnvelopeV1 | null;