@cogcoin/client 1.1.5 → 1.1.7

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 (132) hide show
  1. package/README.md +2 -2
  2. package/dist/bitcoind/indexer-daemon.d.ts +3 -7
  3. package/dist/bitcoind/indexer-daemon.js +39 -204
  4. package/dist/bitcoind/managed-runtime/bitcoind-policy.d.ts +16 -0
  5. package/dist/bitcoind/managed-runtime/bitcoind-policy.js +177 -0
  6. package/dist/bitcoind/managed-runtime/bitcoind-runtime.d.ts +20 -0
  7. package/dist/bitcoind/managed-runtime/bitcoind-runtime.js +74 -0
  8. package/dist/bitcoind/managed-runtime/bitcoind-status.d.ts +11 -0
  9. package/dist/bitcoind/managed-runtime/bitcoind-status.js +44 -0
  10. package/dist/bitcoind/managed-runtime/indexer-policy.d.ts +34 -0
  11. package/dist/bitcoind/managed-runtime/indexer-policy.js +200 -0
  12. package/dist/bitcoind/managed-runtime/indexer-runtime.d.ts +15 -0
  13. package/dist/bitcoind/managed-runtime/indexer-runtime.js +82 -0
  14. package/dist/bitcoind/managed-runtime/status.d.ts +11 -0
  15. package/dist/bitcoind/managed-runtime/status.js +59 -0
  16. package/dist/bitcoind/managed-runtime/types.d.ts +77 -0
  17. package/dist/bitcoind/node.d.ts +2 -2
  18. package/dist/bitcoind/node.js +2 -2
  19. package/dist/bitcoind/rpc.d.ts +2 -1
  20. package/dist/bitcoind/rpc.js +53 -3
  21. package/dist/bitcoind/service.d.ts +2 -7
  22. package/dist/bitcoind/service.js +79 -207
  23. package/dist/cli/command-registry.d.ts +1 -1
  24. package/dist/cli/command-registry.js +2 -64
  25. package/dist/cli/commands/client-admin.js +3 -18
  26. package/dist/cli/commands/mining-runtime.js +4 -60
  27. package/dist/cli/commands/wallet-admin.js +6 -6
  28. package/dist/cli/context.js +1 -3
  29. package/dist/cli/mining-json.d.ts +1 -22
  30. package/dist/cli/mining-json.js +0 -23
  31. package/dist/cli/output.js +16 -2
  32. package/dist/cli/parse.js +0 -2
  33. package/dist/cli/preview-json.d.ts +1 -22
  34. package/dist/cli/preview-json.js +0 -19
  35. package/dist/cli/types.d.ts +1 -3
  36. package/dist/cli/wallet-format.js +1 -1
  37. package/dist/cli/workflow-hints.d.ts +1 -2
  38. package/dist/cli/workflow-hints.js +5 -8
  39. package/dist/wallet/lifecycle/access.d.ts +5 -0
  40. package/dist/wallet/lifecycle/access.js +79 -0
  41. package/dist/wallet/lifecycle/context.d.ts +26 -0
  42. package/dist/wallet/lifecycle/context.js +57 -0
  43. package/dist/wallet/lifecycle/managed-core.d.ts +1 -9
  44. package/dist/wallet/lifecycle/managed-core.js +3 -63
  45. package/dist/wallet/lifecycle/repair-bitcoind.d.ts +10 -0
  46. package/dist/wallet/lifecycle/repair-bitcoind.js +142 -0
  47. package/dist/wallet/lifecycle/repair-indexer.d.ts +8 -0
  48. package/dist/wallet/lifecycle/repair-indexer.js +117 -0
  49. package/dist/wallet/lifecycle/repair-mining.d.ts +1 -5
  50. package/dist/wallet/lifecycle/repair-mining.js +5 -39
  51. package/dist/wallet/lifecycle/repair.d.ts +2 -4
  52. package/dist/wallet/lifecycle/repair.js +74 -318
  53. package/dist/wallet/lifecycle/setup-prompts.d.ts +7 -0
  54. package/dist/wallet/lifecycle/setup-prompts.js +88 -0
  55. package/dist/wallet/lifecycle/setup-state.d.ts +26 -0
  56. package/dist/wallet/lifecycle/setup-state.js +159 -0
  57. package/dist/wallet/lifecycle/setup.d.ts +3 -4
  58. package/dist/wallet/lifecycle/setup.js +47 -351
  59. package/dist/wallet/lifecycle/types.d.ts +33 -5
  60. package/dist/wallet/managed-core-wallet.d.ts +2 -0
  61. package/dist/wallet/managed-core-wallet.js +27 -1
  62. package/dist/wallet/mining/candidate.d.ts +1 -0
  63. package/dist/wallet/mining/candidate.js +38 -6
  64. package/dist/wallet/mining/competitiveness.d.ts +1 -0
  65. package/dist/wallet/mining/competitiveness.js +6 -0
  66. package/dist/wallet/mining/cycle.d.ts +2 -0
  67. package/dist/wallet/mining/cycle.js +14 -4
  68. package/dist/wallet/mining/engine-types.d.ts +1 -0
  69. package/dist/wallet/mining/index.d.ts +1 -1
  70. package/dist/wallet/mining/index.js +1 -1
  71. package/dist/wallet/mining/publish.d.ts +3 -0
  72. package/dist/wallet/mining/publish.js +78 -6
  73. package/dist/wallet/mining/runner.d.ts +0 -32
  74. package/dist/wallet/mining/runner.js +59 -104
  75. package/dist/wallet/mining/stop.d.ts +7 -0
  76. package/dist/wallet/mining/stop.js +23 -0
  77. package/dist/wallet/mining/supervisor.d.ts +2 -36
  78. package/dist/wallet/mining/supervisor.js +139 -246
  79. package/dist/wallet/read/context.d.ts +1 -5
  80. package/dist/wallet/read/context.js +20 -379
  81. package/dist/wallet/read/managed-services.d.ts +33 -0
  82. package/dist/wallet/read/managed-services.js +222 -0
  83. package/dist/wallet/state/client-password/bootstrap.d.ts +2 -0
  84. package/dist/wallet/state/client-password/bootstrap.js +3 -0
  85. package/dist/wallet/state/client-password/context.d.ts +10 -0
  86. package/dist/wallet/state/client-password/context.js +46 -0
  87. package/dist/wallet/state/client-password/crypto.d.ts +34 -0
  88. package/dist/wallet/state/client-password/crypto.js +117 -0
  89. package/dist/wallet/state/client-password/files.d.ts +10 -0
  90. package/dist/wallet/state/client-password/files.js +109 -0
  91. package/dist/wallet/state/client-password/legacy-cleanup.d.ts +11 -0
  92. package/dist/wallet/state/client-password/legacy-cleanup.js +338 -0
  93. package/dist/wallet/state/client-password/messages.d.ts +3 -0
  94. package/dist/wallet/state/client-password/messages.js +9 -0
  95. package/dist/wallet/state/client-password/migration.d.ts +4 -0
  96. package/dist/wallet/state/client-password/migration.js +32 -0
  97. package/dist/wallet/state/client-password/prompts.d.ts +12 -0
  98. package/dist/wallet/state/client-password/prompts.js +79 -0
  99. package/dist/wallet/state/client-password/protected-secrets.d.ts +13 -0
  100. package/dist/wallet/state/client-password/protected-secrets.js +90 -0
  101. package/dist/wallet/state/client-password/readiness.d.ts +4 -0
  102. package/dist/wallet/state/client-password/readiness.js +48 -0
  103. package/dist/wallet/state/client-password/references.d.ts +1 -0
  104. package/dist/wallet/state/client-password/references.js +56 -0
  105. package/dist/wallet/state/client-password/rotation.d.ts +6 -0
  106. package/dist/wallet/state/client-password/rotation.js +98 -0
  107. package/dist/wallet/state/client-password/session-policy.d.ts +6 -0
  108. package/dist/wallet/state/client-password/session-policy.js +28 -0
  109. package/dist/wallet/state/client-password/session.d.ts +19 -0
  110. package/dist/wallet/state/client-password/session.js +170 -0
  111. package/dist/wallet/state/client-password/setup.d.ts +8 -0
  112. package/dist/wallet/state/client-password/setup.js +49 -0
  113. package/dist/wallet/state/client-password/types.d.ts +82 -0
  114. package/dist/wallet/state/client-password/types.js +5 -0
  115. package/dist/wallet/state/client-password.d.ts +7 -38
  116. package/dist/wallet/state/client-password.js +52 -937
  117. package/dist/wallet/tx/anchor.js +123 -216
  118. package/dist/wallet/tx/cog.js +294 -489
  119. package/dist/wallet/tx/common.d.ts +2 -0
  120. package/dist/wallet/tx/common.js +2 -0
  121. package/dist/wallet/tx/domain-admin.js +111 -220
  122. package/dist/wallet/tx/domain-market.js +401 -681
  123. package/dist/wallet/tx/executor.d.ts +176 -0
  124. package/dist/wallet/tx/executor.js +302 -0
  125. package/dist/wallet/tx/field.js +109 -215
  126. package/dist/wallet/tx/register.js +158 -269
  127. package/dist/wallet/tx/reputation.js +120 -227
  128. package/package.json +1 -1
  129. package/dist/wallet/mining/worker-main.js +0 -17
  130. package/dist/wallet/state/client-password-agent.d.ts +0 -1
  131. package/dist/wallet/state/client-password-agent.js +0 -211
  132. /package/dist/{wallet/mining/worker-main.d.ts → bitcoind/managed-runtime/types.js} +0 -0
@@ -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;
@@ -0,0 +1,170 @@
1
+ import { createWrappedSecretEnvelope, decryptWrappedSecretEnvelope, zeroizeBuffer, } from "./crypto.js";
2
+ import { promptForVerifiedClientPassword } from "./prompts.js";
3
+ import { resolveClientPasswordPromptSessionPolicy, resolveClientPasswordSessionUnlockUntilUnixMs, } from "./session-policy.js";
4
+ const activeSessions = new Map();
5
+ let processCleanupRegistered = false;
6
+ function resolveSessionCacheKey(context) {
7
+ return `${context.platform}\n${context.stateRoot}\n${context.directoryPath}\n${context.passwordStatePath}`;
8
+ }
9
+ function clearExpiryTimer(session) {
10
+ if (session.expiryTimer !== null) {
11
+ clearTimeout(session.expiryTimer);
12
+ session.expiryTimer = null;
13
+ }
14
+ }
15
+ function destroySession(session) {
16
+ if (session === undefined) {
17
+ return;
18
+ }
19
+ clearExpiryTimer(session);
20
+ zeroizeBuffer(session.derivedKey);
21
+ }
22
+ function clearSessionByKey(cacheKey) {
23
+ const existing = activeSessions.get(cacheKey);
24
+ if (existing === undefined) {
25
+ return;
26
+ }
27
+ activeSessions.delete(cacheKey);
28
+ destroySession(existing);
29
+ }
30
+ function scheduleSessionExpiry(cacheKey, session) {
31
+ clearExpiryTimer(session);
32
+ if (session.unlockUntilUnixMs === null) {
33
+ return;
34
+ }
35
+ const remainingMs = Math.max(0, session.unlockUntilUnixMs - Date.now());
36
+ session.expiryTimer = setTimeout(() => {
37
+ clearSessionByKey(cacheKey);
38
+ }, remainingMs);
39
+ session.expiryTimer.unref();
40
+ }
41
+ export function destroyAllClientPasswordSessionsResolved() {
42
+ for (const session of activeSessions.values()) {
43
+ destroySession(session);
44
+ }
45
+ activeSessions.clear();
46
+ }
47
+ function registerProcessCleanup() {
48
+ if (processCleanupRegistered) {
49
+ return;
50
+ }
51
+ processCleanupRegistered = true;
52
+ process.once("exit", () => {
53
+ destroyAllClientPasswordSessionsResolved();
54
+ });
55
+ }
56
+ function getActiveSession(context) {
57
+ const cacheKey = resolveSessionCacheKey(context);
58
+ const session = activeSessions.get(cacheKey);
59
+ if (session === undefined) {
60
+ return null;
61
+ }
62
+ if (session.unlockUntilUnixMs !== null && session.unlockUntilUnixMs <= Date.now()) {
63
+ clearSessionByKey(cacheKey);
64
+ return null;
65
+ }
66
+ return session;
67
+ }
68
+ function putActiveSession(options) {
69
+ registerProcessCleanup();
70
+ const cacheKey = resolveSessionCacheKey(options);
71
+ clearSessionByKey(cacheKey);
72
+ if (options.unlockUntilUnixMs !== null && options.unlockUntilUnixMs <= Date.now()) {
73
+ return {
74
+ unlocked: false,
75
+ unlockUntilUnixMs: null,
76
+ };
77
+ }
78
+ const session = {
79
+ derivedKey: Buffer.from(options.derivedKey),
80
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
81
+ expiryTimer: null,
82
+ };
83
+ activeSessions.set(cacheKey, session);
84
+ scheduleSessionExpiry(cacheKey, session);
85
+ return {
86
+ unlocked: true,
87
+ unlockUntilUnixMs: session.unlockUntilUnixMs,
88
+ };
89
+ }
90
+ export async function readClientPasswordSessionStatusResolved(context) {
91
+ const session = getActiveSession(context);
92
+ return {
93
+ unlocked: session !== null,
94
+ unlockUntilUnixMs: session?.unlockUntilUnixMs ?? null,
95
+ };
96
+ }
97
+ export async function lockClientPasswordSessionResolved(context) {
98
+ clearSessionByKey(resolveSessionCacheKey(context));
99
+ return {
100
+ unlocked: false,
101
+ unlockUntilUnixMs: null,
102
+ };
103
+ }
104
+ export async function startClientPasswordSessionResolved(options) {
105
+ return await startClientPasswordSessionWithExpiryResolved({
106
+ ...options,
107
+ unlockUntilUnixMs: resolveClientPasswordSessionUnlockUntilUnixMs(options.sessionPolicy),
108
+ });
109
+ }
110
+ export async function startClientPasswordSessionWithExpiryResolved(options) {
111
+ try {
112
+ return putActiveSession(options);
113
+ }
114
+ finally {
115
+ zeroizeBuffer(options.derivedKey);
116
+ }
117
+ }
118
+ async function refreshClientPasswordSessionResolved(context) {
119
+ const session = getActiveSession(context);
120
+ if (session === null) {
121
+ return null;
122
+ }
123
+ session.unlockUntilUnixMs = context.unlockUntilUnixMs;
124
+ scheduleSessionExpiry(resolveSessionCacheKey(context), session);
125
+ return await readClientPasswordSessionStatusResolved(context);
126
+ }
127
+ async function unlockClientPasswordSessionWithPromptResolved(options) {
128
+ const derivedKey = await promptForVerifiedClientPassword({
129
+ context: options.context,
130
+ prompt: options.prompt,
131
+ promptMessage: "Client password: ",
132
+ ttyErrorCode: "wallet_client_password_unlock_requires_tty",
133
+ });
134
+ return await startClientPasswordSessionResolved({
135
+ ...options.context,
136
+ derivedKey,
137
+ sessionPolicy: resolveClientPasswordPromptSessionPolicy(options.prompt),
138
+ });
139
+ }
140
+ export async function unlockClientPasswordSessionResolved(options) {
141
+ const sessionPolicy = resolveClientPasswordPromptSessionPolicy(options.prompt);
142
+ const currentStatus = await readClientPasswordSessionStatusResolved(options.context);
143
+ if (currentStatus.unlocked) {
144
+ const refreshed = await refreshClientPasswordSessionResolved({
145
+ ...options.context,
146
+ unlockUntilUnixMs: resolveClientPasswordSessionUnlockUntilUnixMs(sessionPolicy),
147
+ });
148
+ if (refreshed !== null) {
149
+ return refreshed;
150
+ }
151
+ }
152
+ if (!options.prompt.isInteractive) {
153
+ throw new Error("wallet_client_password_unlock_requires_tty");
154
+ }
155
+ return await unlockClientPasswordSessionWithPromptResolved(options);
156
+ }
157
+ export function decryptClientProtectedSecretWithSessionResolved(context, envelope) {
158
+ const session = getActiveSession(context);
159
+ if (session === null) {
160
+ return null;
161
+ }
162
+ return new Uint8Array(decryptWrappedSecretEnvelope(envelope, session.derivedKey));
163
+ }
164
+ export function encryptClientProtectedSecretWithSessionResolved(context, secret) {
165
+ const session = getActiveSession(context);
166
+ if (session === null) {
167
+ return null;
168
+ }
169
+ return createWrappedSecretEnvelope(secret, session.derivedKey);
170
+ }
@@ -0,0 +1,8 @@
1
+ import type { ClientPasswordPrompt, ClientPasswordResolvedContext, ClientPasswordSessionStatus, ClientPasswordSetupAction } from "./types.js";
2
+ export declare function ensureClientPasswordConfiguredResolved(options: {
3
+ context: ClientPasswordResolvedContext;
4
+ prompt: ClientPasswordPrompt;
5
+ }): Promise<{
6
+ action: ClientPasswordSetupAction;
7
+ session: ClientPasswordSessionStatus;
8
+ }>;
@@ -0,0 +1,49 @@
1
+ import { mkdir } from "node:fs/promises";
2
+ import { createClientPasswordState, zeroizeBuffer, } from "./crypto.js";
3
+ import { writeClientPasswordState } from "./files.js";
4
+ import { migrateReferencedSecrets } from "./migration.js";
5
+ import { promptForNewPassword } from "./prompts.js";
6
+ import { inspectClientPasswordReadinessResolved } from "./readiness.js";
7
+ import { resolveClientPasswordPromptSessionPolicy } from "./session-policy.js";
8
+ import { finalizePendingClientPasswordRotationIfNeeded } from "./rotation.js";
9
+ import { startClientPasswordSessionResolved } from "./session.js";
10
+ import { readClientPasswordSessionStatusResolved } from "./session.js";
11
+ export async function ensureClientPasswordConfiguredResolved(options) {
12
+ await finalizePendingClientPasswordRotationIfNeeded(options.context);
13
+ const readiness = await inspectClientPasswordReadinessResolved(options.context);
14
+ if (readiness === "ready") {
15
+ return {
16
+ action: "already-configured",
17
+ session: await readClientPasswordSessionStatusResolved(options.context),
18
+ };
19
+ }
20
+ const setup = await promptForNewPassword(options.prompt);
21
+ let derivedKey = null;
22
+ try {
23
+ const created = await createClientPasswordState({
24
+ passwordBytes: setup.passwordBytes,
25
+ passwordHint: setup.passwordHint,
26
+ });
27
+ derivedKey = created.derivedKey;
28
+ await mkdir(options.context.directoryPath, { recursive: true, mode: 0o700 });
29
+ await writeClientPasswordState(options.context.passwordStatePath, created.state);
30
+ const migrated = await migrateReferencedSecrets({
31
+ ...options.context,
32
+ derivedKey,
33
+ });
34
+ const session = await startClientPasswordSessionResolved({
35
+ ...options.context,
36
+ derivedKey,
37
+ sessionPolicy: resolveClientPasswordPromptSessionPolicy(options.prompt),
38
+ });
39
+ derivedKey = null;
40
+ return {
41
+ action: migrated || readiness === "migration-required" ? "migrated" : "created",
42
+ session,
43
+ };
44
+ }
45
+ finally {
46
+ zeroizeBuffer(setup.passwordBytes);
47
+ zeroizeBuffer(derivedKey);
48
+ }
49
+ }
@@ -0,0 +1,82 @@
1
+ export declare const CLIENT_PASSWORD_STATE_FORMAT = "cogcoin-client-password";
2
+ export declare const CLIENT_PASSWORD_ROTATION_JOURNAL_FORMAT = "cogcoin-client-password-rotation";
3
+ export declare const CLIENT_PASSWORD_VERIFIER_FORMAT = "cogcoin-client-password-verifier";
4
+ export declare const LOCAL_SECRET_ENVELOPE_FORMAT = "cogcoin-local-wallet-secret";
5
+ export declare const CLIENT_PASSWORD_VERIFIER_TEXT = "cogcoin-client-password-verifier-v1";
6
+ export type ClientPasswordReadiness = "ready" | "setup-required" | "migration-required";
7
+ export type ClientPasswordSetupAction = "created" | "migrated" | "already-configured";
8
+ export interface ClientPasswordPrompt {
9
+ readonly isInteractive: boolean;
10
+ writeLine(message: string): void;
11
+ prompt(message: string): Promise<string>;
12
+ promptHidden?(message: string): Promise<string>;
13
+ }
14
+ export interface ClientPasswordSessionStatus {
15
+ unlocked: boolean;
16
+ unlockUntilUnixMs: number | null;
17
+ }
18
+ export interface ClientPasswordLegacyKeychainReader {
19
+ loadSecret(keyId: string): Promise<Uint8Array>;
20
+ }
21
+ export interface ClientPasswordStorageOptions {
22
+ platform: NodeJS.Platform;
23
+ stateRoot: string;
24
+ runtimeRoot: string;
25
+ directoryPath: string;
26
+ runtimeErrorCode: string;
27
+ legacyMacKeychainReader?: ClientPasswordLegacyKeychainReader | null;
28
+ }
29
+ export interface ClientPasswordResolvedContext extends ClientPasswordStorageOptions {
30
+ legacyMacKeychainReader?: ClientPasswordLegacyKeychainReader | null;
31
+ passwordStatePath: string;
32
+ rotationJournalPath: string;
33
+ }
34
+ export interface ClientPasswordStateV1 {
35
+ format: typeof CLIENT_PASSWORD_STATE_FORMAT;
36
+ version: 1;
37
+ passwordHint: string;
38
+ kdf: {
39
+ name: "argon2id";
40
+ memoryKib: number;
41
+ iterations: number;
42
+ parallelism: number;
43
+ salt: string;
44
+ };
45
+ verifier: {
46
+ cipher: "aes-256-gcm";
47
+ nonce: string;
48
+ tag: string;
49
+ ciphertext: string;
50
+ };
51
+ }
52
+ export interface WrappedSecretEnvelopeV1 {
53
+ format: typeof LOCAL_SECRET_ENVELOPE_FORMAT;
54
+ version: 1;
55
+ cipher: "aes-256-gcm";
56
+ wrappedBy: "client-password";
57
+ nonce: string;
58
+ tag: string;
59
+ ciphertext: string;
60
+ }
61
+ export interface ClientPasswordRotationJournalV1 {
62
+ format: typeof CLIENT_PASSWORD_ROTATION_JOURNAL_FORMAT;
63
+ version: 1;
64
+ nextState: ClientPasswordStateV1;
65
+ secrets: Array<{
66
+ keyId: string;
67
+ envelope: WrappedSecretEnvelopeV1;
68
+ }>;
69
+ }
70
+ export interface ClientPasswordAgentBootstrapState {
71
+ unlockUntilUnixMs: number;
72
+ derivedKeyBase64: string;
73
+ }
74
+ export type LocalSecretFile = {
75
+ state: "missing";
76
+ } | {
77
+ state: "raw";
78
+ secret: Uint8Array;
79
+ } | {
80
+ state: "wrapped";
81
+ envelope: WrappedSecretEnvelopeV1;
82
+ };
@@ -0,0 +1,5 @@
1
+ export const CLIENT_PASSWORD_STATE_FORMAT = "cogcoin-client-password";
2
+ export const CLIENT_PASSWORD_ROTATION_JOURNAL_FORMAT = "cogcoin-client-password-rotation";
3
+ export const CLIENT_PASSWORD_VERIFIER_FORMAT = "cogcoin-client-password-verifier";
4
+ export const LOCAL_SECRET_ENVELOPE_FORMAT = "cogcoin-local-wallet-secret";
5
+ export const CLIENT_PASSWORD_VERIFIER_TEXT = "cogcoin-client-password-verifier-v1";
@@ -1,27 +1,10 @@
1
- export declare const CLIENT_PASSWORD_SETUP_AUTO_UNLOCK_SECONDS = 86400;
2
- export type ClientPasswordReadiness = "ready" | "setup-required" | "migration-required";
3
- export type ClientPasswordSetupAction = "created" | "migrated" | "already-configured";
4
- export interface ClientPasswordPrompt {
5
- readonly isInteractive: boolean;
6
- writeLine(message: string): void;
7
- prompt(message: string): Promise<string>;
8
- promptHidden?(message: string): Promise<string>;
9
- }
10
- export interface ClientPasswordSessionStatus {
11
- unlocked: boolean;
12
- unlockUntilUnixMs: number | null;
13
- }
14
- export interface ClientPasswordStorageOptions {
15
- platform: NodeJS.Platform;
16
- stateRoot: string;
17
- runtimeRoot: string;
18
- directoryPath: string;
19
- runtimeErrorCode: string;
20
- legacyMacKeychainReader?: {
21
- loadSecret(keyId: string): Promise<Uint8Array>;
22
- } | null;
23
- }
24
- export declare function resolveLocalSecretFilePath(directoryPath: string, keyId: string): string;
1
+ export { CLIENT_PASSWORD_SETUP_AUTO_UNLOCK_SECONDS } from "./client-password/crypto.js";
2
+ export type { ClientPasswordPrompt, ClientPasswordReadiness, ClientPasswordSessionStatus, ClientPasswordSetupAction, ClientPasswordStorageOptions, } from "./client-password/types.js";
3
+ export { resolveLocalSecretFilePath, createLegacyKeychainServiceName, } from "./client-password/context.js";
4
+ export { createAgentBootstrapState } from "./client-password/bootstrap.js";
5
+ export { describeClientPasswordLockedMessage, describeClientPasswordMigrationMessage, describeClientPasswordSetupMessage, } from "./client-password/messages.js";
6
+ export { listLocalSecretFilesForTesting } from "./client-password/files.js";
7
+ import type { ClientPasswordPrompt, ClientPasswordReadiness, ClientPasswordSessionStatus, ClientPasswordSetupAction, ClientPasswordStorageOptions } from "./client-password/types.js";
25
8
  export declare function inspectClientPasswordReadiness(options: ClientPasswordStorageOptions): Promise<ClientPasswordReadiness>;
26
9
  export declare function readClientPasswordSessionStatus(options: ClientPasswordStorageOptions): Promise<ClientPasswordSessionStatus>;
27
10
  export declare function lockClientPasswordSession(options: ClientPasswordStorageOptions): Promise<ClientPasswordSessionStatus>;
@@ -49,17 +32,3 @@ export declare function unlockClientPasswordSession(options: ClientPasswordStora
49
32
  export declare function changeClientPassword(options: ClientPasswordStorageOptions & {
50
33
  prompt: ClientPasswordPrompt;
51
34
  }): Promise<ClientPasswordSessionStatus>;
52
- export declare function createLegacyKeychainServiceName(): string;
53
- export declare function createAgentBootstrapState(options: {
54
- unlockUntilUnixMs: number;
55
- derivedKeyBase64: string;
56
- }): {
57
- unlockUntilUnixMs: number;
58
- derivedKeyBase64: string;
59
- };
60
- export declare function describeClientPasswordLockedMessage(): string;
61
- export declare function describeClientPasswordSetupMessage(): string;
62
- export declare function describeClientPasswordMigrationMessage(): string;
63
- export declare function listLocalSecretFilesForTesting(options: {
64
- directoryPath: string;
65
- }): Promise<string[]>;