@cello-protocol/daemon 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-loader.d.ts +41 -0
- package/dist/agent-loader.d.ts.map +1 -0
- package/dist/agent-loader.js +94 -0
- package/dist/agent-loader.js.map +1 -0
- package/dist/bin/cello-daemon.d.ts +13 -0
- package/dist/bin/cello-daemon.d.ts.map +1 -0
- package/dist/bin/cello-daemon.js +170 -0
- package/dist/bin/cello-daemon.js.map +1 -0
- package/dist/cello-node-transport-dialer.d.ts +59 -0
- package/dist/cello-node-transport-dialer.d.ts.map +1 -0
- package/dist/cello-node-transport-dialer.js +108 -0
- package/dist/cello-node-transport-dialer.js.map +1 -0
- package/dist/challenge-verifier.d.ts +12 -0
- package/dist/challenge-verifier.d.ts.map +1 -0
- package/dist/challenge-verifier.js +11 -0
- package/dist/challenge-verifier.js.map +1 -0
- package/dist/connect-or-start.d.ts +25 -0
- package/dist/connect-or-start.d.ts.map +1 -0
- package/dist/connect-or-start.js +117 -0
- package/dist/connect-or-start.js.map +1 -0
- package/dist/content-park-client.d.ts +49 -0
- package/dist/content-park-client.d.ts.map +1 -0
- package/dist/content-park-client.js +196 -0
- package/dist/content-park-client.js.map +1 -0
- package/dist/daemon.d.ts +65 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +3202 -0
- package/dist/daemon.js.map +1 -0
- package/dist/directory-bootstrap.d.ts +55 -0
- package/dist/directory-bootstrap.d.ts.map +1 -0
- package/dist/directory-bootstrap.js +102 -0
- package/dist/directory-bootstrap.js.map +1 -0
- package/dist/file-manifest-provider.d.ts +18 -0
- package/dist/file-manifest-provider.d.ts.map +1 -0
- package/dist/file-manifest-provider.js +72 -0
- package/dist/file-manifest-provider.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/ipc-client.d.ts +31 -0
- package/dist/ipc-client.d.ts.map +1 -0
- package/dist/ipc-client.js +112 -0
- package/dist/ipc-client.js.map +1 -0
- package/dist/ipc-server.d.ts +49 -0
- package/dist/ipc-server.d.ts.map +1 -0
- package/dist/ipc-server.js +268 -0
- package/dist/ipc-server.js.map +1 -0
- package/dist/lock-file.d.ts +27 -0
- package/dist/lock-file.d.ts.map +1 -0
- package/dist/lock-file.js +84 -0
- package/dist/lock-file.js.map +1 -0
- package/dist/manifest-loader.d.ts +33 -0
- package/dist/manifest-loader.d.ts.map +1 -0
- package/dist/manifest-loader.js +70 -0
- package/dist/manifest-loader.js.map +1 -0
- package/dist/manifest-poll-scheduler.d.ts +31 -0
- package/dist/manifest-poll-scheduler.d.ts.map +1 -0
- package/dist/manifest-poll-scheduler.js +59 -0
- package/dist/manifest-poll-scheduler.js.map +1 -0
- package/dist/manifest-version-store-file.d.ts +18 -0
- package/dist/manifest-version-store-file.d.ts.map +1 -0
- package/dist/manifest-version-store-file.js +40 -0
- package/dist/manifest-version-store-file.js.map +1 -0
- package/dist/manifest-version-store.d.ts +14 -0
- package/dist/manifest-version-store.d.ts.map +1 -0
- package/dist/manifest-version-store.js +13 -0
- package/dist/manifest-version-store.js.map +1 -0
- package/dist/network-directory-node.d.ts +94 -0
- package/dist/network-directory-node.d.ts.map +1 -0
- package/dist/network-directory-node.js +626 -0
- package/dist/network-directory-node.js.map +1 -0
- package/dist/nonce-dedup.d.ts +68 -0
- package/dist/nonce-dedup.d.ts.map +1 -0
- package/dist/nonce-dedup.js +204 -0
- package/dist/nonce-dedup.js.map +1 -0
- package/dist/notification-dispatcher.d.ts +65 -0
- package/dist/notification-dispatcher.d.ts.map +1 -0
- package/dist/notification-dispatcher.js +138 -0
- package/dist/notification-dispatcher.js.map +1 -0
- package/dist/registration-context.d.ts +69 -0
- package/dist/registration-context.d.ts.map +1 -0
- package/dist/registration-context.js +118 -0
- package/dist/registration-context.js.map +1 -0
- package/dist/registration-manager.d.ts +72 -0
- package/dist/registration-manager.d.ts.map +1 -0
- package/dist/registration-manager.js +267 -0
- package/dist/registration-manager.js.map +1 -0
- package/dist/registration-persistence.d.ts +131 -0
- package/dist/registration-persistence.d.ts.map +1 -0
- package/dist/registration-persistence.js +233 -0
- package/dist/registration-persistence.js.map +1 -0
- package/dist/retry-queue.d.ts +144 -0
- package/dist/retry-queue.d.ts.map +1 -0
- package/dist/retry-queue.js +444 -0
- package/dist/retry-queue.js.map +1 -0
- package/dist/seal-frontier-verify.d.ts +58 -0
- package/dist/seal-frontier-verify.d.ts.map +1 -0
- package/dist/seal-frontier-verify.js +87 -0
- package/dist/seal-frontier-verify.js.map +1 -0
- package/dist/seal-legibility-tbs.d.ts +25 -0
- package/dist/seal-legibility-tbs.d.ts.map +1 -0
- package/dist/seal-legibility-tbs.js +78 -0
- package/dist/seal-legibility-tbs.js.map +1 -0
- package/dist/seal-upgrade.d.ts +90 -0
- package/dist/seal-upgrade.d.ts.map +1 -0
- package/dist/seal-upgrade.js +178 -0
- package/dist/seal-upgrade.js.map +1 -0
- package/dist/session-assignment-parser.d.ts +22 -0
- package/dist/session-assignment-parser.d.ts.map +1 -0
- package/dist/session-assignment-parser.js +139 -0
- package/dist/session-assignment-parser.js.map +1 -0
- package/dist/session-ceremony.d.ts +156 -0
- package/dist/session-ceremony.d.ts.map +1 -0
- package/dist/session-ceremony.js +447 -0
- package/dist/session-ceremony.js.map +1 -0
- package/dist/session-connection-gater.d.ts +91 -0
- package/dist/session-connection-gater.d.ts.map +1 -0
- package/dist/session-connection-gater.js +146 -0
- package/dist/session-connection-gater.js.map +1 -0
- package/dist/session-node-manager.d.ts +585 -0
- package/dist/session-node-manager.d.ts.map +1 -0
- package/dist/session-node-manager.js +2609 -0
- package/dist/session-node-manager.js.map +1 -0
- package/dist/session-relay-client.d.ts +101 -0
- package/dist/session-relay-client.d.ts.map +1 -0
- package/dist/session-relay-client.js +520 -0
- package/dist/session-relay-client.js.map +1 -0
- package/dist/session-tree.d.ts +80 -0
- package/dist/session-tree.d.ts.map +1 -0
- package/dist/session-tree.js +123 -0
- package/dist/session-tree.js.map +1 -0
- package/dist/signaling-connect.d.ts +83 -0
- package/dist/signaling-connect.d.ts.map +1 -0
- package/dist/signaling-connect.js +266 -0
- package/dist/signaling-connect.js.map +1 -0
- package/dist/transcript-cipher.d.ts +31 -0
- package/dist/transcript-cipher.d.ts.map +1 -0
- package/dist/transcript-cipher.js +74 -0
- package/dist/transcript-cipher.js.map +1 -0
- package/dist/transport-composition.d.ts +31 -0
- package/dist/transport-composition.d.ts.map +1 -0
- package/dist/transport-composition.js +55 -0
- package/dist/transport-composition.js.map +1 -0
- package/dist/transport-selector.d.ts +189 -0
- package/dist/transport-selector.d.ts.map +1 -0
- package/dist/transport-selector.js +195 -0
- package/dist/transport-selector.js.map +1 -0
- package/dist/types.d.ts +265 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +33 -0
- package/dist/types.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CELLO Daemon — SessionTree (CELLO-M7-DAEMON-004, Option A)
|
|
3
|
+
*
|
|
4
|
+
* The DAEMON-OWNED per-session Merkle tree. This is the authoritative running
|
|
5
|
+
* transcript of every leaf (sent or received) for a session. Before this story,
|
|
6
|
+
* the daemon "did not maintain the session Merkle tree — the client supplied it"
|
|
7
|
+
* (daemon.ts:568). Option A re-homes ownership here: the daemon is the component
|
|
8
|
+
* that sends and receives every message, so the transcript belongs where the
|
|
9
|
+
* messages flow.
|
|
10
|
+
*
|
|
11
|
+
* Specification (SPARC Phase S):
|
|
12
|
+
* - A leaf is the 32-byte leaf hash of a message or control entry, in order.
|
|
13
|
+
* For a message leaf the leaf hash is content_hash = SHA-256(0x00 || content)
|
|
14
|
+
* (msgLeafHash, RFC 6962 §2.1 leaf hashing). For a control leaf (SEAL) the
|
|
15
|
+
* leaf hash is SHA-256(0x02 || canonical_bytes) (ctrlLeafHash). Both sides of
|
|
16
|
+
* a session compute the SAME leaf hash for the SAME message, so appending the
|
|
17
|
+
* SAME leaves in the SAME order yields the SAME root (AC-002).
|
|
18
|
+
* - The root is recomputed over all leaves after every append (RFC 6962 §2.1,
|
|
19
|
+
* left-balanced binary Merkle tree). Empty tree root = SHA-256("").
|
|
20
|
+
* - The tree is serializable to/from an ordered list of leaf-hash hex strings so
|
|
21
|
+
* it can be persisted to SQLCipher and reloaded across a daemon restart (AC-007).
|
|
22
|
+
*
|
|
23
|
+
* Pseudocode (SPARC Phase P):
|
|
24
|
+
* appendLeafHash(kind, hashHex):
|
|
25
|
+
* 1. push {kind, hashHex} onto leaves
|
|
26
|
+
* 2. return { leafIndex: leaves.length - 1, newRootHex: rootHex() }
|
|
27
|
+
* rootHex():
|
|
28
|
+
* 1. build = buildMerkleTree(leaves.map(l => {kind:"hash", data: bytes(l.hashHex)}))
|
|
29
|
+
* 2. return hex(merkleRoot(build)) // empty → hex(SHA-256(""))
|
|
30
|
+
* fromLeaves(leaves): reconstruct from persisted ordered list.
|
|
31
|
+
*
|
|
32
|
+
* Crypto refs: RFC 6962 §2.1 (Merkle hash trees), FIPS 180-4 (SHA-256).
|
|
33
|
+
*/
|
|
34
|
+
import { buildMerkleTree, merkleRoot } from "@cello-protocol/crypto";
|
|
35
|
+
/**
|
|
36
|
+
* A daemon-owned per-session Merkle tree. Leaves are stored as pre-computed
|
|
37
|
+
* 32-byte leaf hashes (hex), so reconstruction is exact regardless of the
|
|
38
|
+
* original payload — the daemon never needs the plaintext to recompute the root.
|
|
39
|
+
*/
|
|
40
|
+
export class SessionTree {
|
|
41
|
+
#leaves;
|
|
42
|
+
// O(1) content-hash -> first leaf index, so the inbound dedup check (DOD-MSG-5) is not a linear
|
|
43
|
+
// scan per message (review finding #5). Stores the FIRST occurrence to match findIndex semantics.
|
|
44
|
+
// Rebuilt from #leaves in the constructor, so it survives the fromLeaves() restart path for free.
|
|
45
|
+
#indexByHash;
|
|
46
|
+
constructor(leaves) {
|
|
47
|
+
this.#leaves = leaves;
|
|
48
|
+
this.#indexByHash = new Map();
|
|
49
|
+
for (let i = 0; i < leaves.length; i++) {
|
|
50
|
+
if (!this.#indexByHash.has(leaves[i].hashHex))
|
|
51
|
+
this.#indexByHash.set(leaves[i].hashHex, i);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** O(1) index of the first leaf with this content-hash, or -1 if absent (DOD-MSG-5 dedup). */
|
|
55
|
+
indexOfHash(hashHex) {
|
|
56
|
+
return this.#indexByHash.get(hashHex) ?? -1;
|
|
57
|
+
}
|
|
58
|
+
/** Construct an empty tree. */
|
|
59
|
+
static empty() {
|
|
60
|
+
return new SessionTree([]);
|
|
61
|
+
}
|
|
62
|
+
/** Reconstruct a tree from a persisted, ordered list of leaves (AC-007). */
|
|
63
|
+
static fromLeaves(leaves) {
|
|
64
|
+
return new SessionTree(leaves.map((l) => ({ kind: l.kind, hashHex: l.hashHex })));
|
|
65
|
+
}
|
|
66
|
+
/** Number of leaves currently in the tree. */
|
|
67
|
+
size() {
|
|
68
|
+
return this.#leaves.length;
|
|
69
|
+
}
|
|
70
|
+
/** Ordered copy of the leaves (for persistence / inspection). */
|
|
71
|
+
leaves() {
|
|
72
|
+
return this.#leaves.map((l) => ({ kind: l.kind, hashHex: l.hashHex }));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Append a leaf (by its pre-computed 32-byte leaf-hash hex) and advance the root.
|
|
76
|
+
* @returns the new leaf's index and the recomputed root hex.
|
|
77
|
+
*/
|
|
78
|
+
appendLeafHash(kind, hashHex) {
|
|
79
|
+
if (!/^[0-9a-f]{64}$/.test(hashHex)) {
|
|
80
|
+
throw new Error(`SessionTree.appendLeafHash: hashHex must be 64 lowercase hex chars (32 bytes), got length ${hashHex.length}`);
|
|
81
|
+
}
|
|
82
|
+
this.#leaves.push({ kind, hashHex });
|
|
83
|
+
const leafIndex = this.#leaves.length - 1;
|
|
84
|
+
if (!this.#indexByHash.has(hashHex))
|
|
85
|
+
this.#indexByHash.set(hashHex, leafIndex);
|
|
86
|
+
return { leafIndex, newRootHex: this.rootHex() };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Recompute and return the current Merkle root as hex.
|
|
90
|
+
* Empty tree → hex(SHA-256("")) per RFC 6962 §2.1.
|
|
91
|
+
*/
|
|
92
|
+
rootHex() {
|
|
93
|
+
const inputs = this.#leaves.map((l) => ({
|
|
94
|
+
kind: "hash",
|
|
95
|
+
data: hexToBytes(l.hashHex),
|
|
96
|
+
}));
|
|
97
|
+
const tree = buildMerkleTree(inputs);
|
|
98
|
+
return bytesToHex(merkleRoot(tree));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* SESSION-002: the Merkle root this tree WOULD have if one more leaf (hashHex) were
|
|
102
|
+
* appended — WITHOUT mutating the tree. Used to compute the reported_root for a unilateral
|
|
103
|
+
* seal: the post-SEAL-ctrl-leaf root the directory rebuilds from the relay's content-hash
|
|
104
|
+
* chain and verifies, without advancing the durable tree / message_count.
|
|
105
|
+
*/
|
|
106
|
+
rootWithAppendedHex(hashHex) {
|
|
107
|
+
if (!/^[0-9a-f]{64}$/.test(hashHex)) {
|
|
108
|
+
throw new Error(`SessionTree.rootWithAppendedHex: hashHex must be 64 lowercase hex chars (32 bytes), got length ${hashHex.length}`);
|
|
109
|
+
}
|
|
110
|
+
const inputs = [...this.#leaves, { kind: "ctrl", hashHex }].map((l) => ({
|
|
111
|
+
kind: "hash",
|
|
112
|
+
data: hexToBytes(l.hashHex),
|
|
113
|
+
}));
|
|
114
|
+
return bytesToHex(merkleRoot(buildMerkleTree(inputs)));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function hexToBytes(hex) {
|
|
118
|
+
return new Uint8Array(Buffer.from(hex, "hex"));
|
|
119
|
+
}
|
|
120
|
+
function bytesToHex(bytes) {
|
|
121
|
+
return Buffer.from(bytes).toString("hex");
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=session-tree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-tree.js","sourceRoot":"","sources":["../src/session-tree.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M7 Keystone (Part 2) — production `signalingConnect` for the daemon.
|
|
3
|
+
*
|
|
4
|
+
* This is the daemon's directory-facing dialer. It establishes ONE authenticated
|
|
5
|
+
* signaling stream to a directory node and returns it as a transport
|
|
6
|
+
* `ConnectResult`. The transport `SignalingManager` owns everything after connect:
|
|
7
|
+
* heartbeat, reconnect (it calls connect() again on a fresh attempt), the outbound
|
|
8
|
+
* queue, and optional manifest polling.
|
|
9
|
+
*
|
|
10
|
+
* It is a faithful port of the proven M6 client handshake in
|
|
11
|
+
* `core/client/src/signaling-manager.ts` (`#doOpen`, ~lines 527-643). That path
|
|
12
|
+
* connected the M6 client to the real directory, ran the full DKG ceremony, and
|
|
13
|
+
* sealed sessions — so the 7-step handshake here is the known-good one, not new
|
|
14
|
+
* protocol.
|
|
15
|
+
*
|
|
16
|
+
* Architecture note (2026-06-11 daemon-transport doc): this is the *directory-facing
|
|
17
|
+
* node* — one per daemon, signaling only. A FRESH libp2p node (and therefore a fresh
|
|
18
|
+
* transport key / Peer ID) is created on every connect, which is exactly the
|
|
19
|
+
* reconnect-rotation behavior the architecture calls for. Per-session data exchange
|
|
20
|
+
* uses separate ephemeral session nodes, not this node.
|
|
21
|
+
*
|
|
22
|
+
* Step-5/6 directory identity verification (the consortium-manifest "directory proves
|
|
23
|
+
* itself back" hardening) is OPTIONAL here: it runs only when a `challengeVerifier`
|
|
24
|
+
* is supplied. M6 ran with it off (challengeVerifier = null) and connected fine; the
|
|
25
|
+
* hardening layers on later without changing this path.
|
|
26
|
+
*
|
|
27
|
+
* Crypto reference: agent→directory auth signs SHA-256(domain ‖ nonce ‖ pubkey) with
|
|
28
|
+
* the agent's K_local Ed25519 key (RFC 8032). K_local never leaves the KeyProvider.
|
|
29
|
+
*/
|
|
30
|
+
import { type CelloNode, type ConnectResult, type IDirectoryChallengeVerifier } from "@cello-protocol/transport";
|
|
31
|
+
import type { KeyProvider } from "@cello-protocol/crypto";
|
|
32
|
+
import type { Logger } from "./types.js";
|
|
33
|
+
/** A directory node the daemon can dial, resolved from bootstrap config or a manifest. */
|
|
34
|
+
export interface DirectoryEndpoint {
|
|
35
|
+
peerId: string;
|
|
36
|
+
/** A dialable multiaddr (e.g. /dns4/host/tcp/443/wss/p2p/<peerId>). Optional if already connected. */
|
|
37
|
+
multiaddr?: string;
|
|
38
|
+
}
|
|
39
|
+
/** The agent identity that authenticates this signaling stream (steps 3-4). */
|
|
40
|
+
export interface SignalingAuthIdentity {
|
|
41
|
+
keyProvider: KeyProvider;
|
|
42
|
+
pubkeyHex: string;
|
|
43
|
+
}
|
|
44
|
+
export interface SignalingConnectDeps {
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the directory node to dial. Returns null when no endpoint is known yet.
|
|
47
|
+
* May be async — production re-resolves the bootstrap (GET /bootstrap) per connect
|
|
48
|
+
* so a directory address change is picked up on the next reconnect.
|
|
49
|
+
*/
|
|
50
|
+
getDirectoryEndpoint: () => DirectoryEndpoint | null | Promise<DirectoryEndpoint | null>;
|
|
51
|
+
/** Resolve the agent identity to authenticate as. Returns null when no agent exists yet. */
|
|
52
|
+
getAuthIdentity: () => SignalingAuthIdentity | null;
|
|
53
|
+
logger: Logger;
|
|
54
|
+
/**
|
|
55
|
+
* Optional directory identity verifier (consortium-manifest step-6 hardening).
|
|
56
|
+
* When absent, directory verification is skipped — the M6 backward-compat path.
|
|
57
|
+
*/
|
|
58
|
+
challengeVerifier?: IDirectoryChallengeVerifier;
|
|
59
|
+
/** Verified consortium manifest version, surfaced in ConnectResult. Defaults to 0 (no manifest). */
|
|
60
|
+
getManifestVersion?: () => number;
|
|
61
|
+
/**
|
|
62
|
+
* Test seam: inject a node factory. Production uses `createNode` to mint a fresh
|
|
63
|
+
* directory-facing node per connect.
|
|
64
|
+
*/
|
|
65
|
+
createDirectoryNode?: (keyProvider: KeyProvider) => Promise<CelloNode>;
|
|
66
|
+
/**
|
|
67
|
+
* M7 Action 2: publish the live directory-facing node to the daemon so subsystems
|
|
68
|
+
* that must reach the directory on the SAME node — registration's FROST DKG
|
|
69
|
+
* (NetworkDirectoryNode), ceremonies, seal — can use it. Called with the node on a
|
|
70
|
+
* successful connect, and with `null` when the stream closes. The daemon keeps the
|
|
71
|
+
* latest reference and gates use on the signaling status being `connected`.
|
|
72
|
+
*/
|
|
73
|
+
publishNode?: (node: CelloNode | null) => void;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Build a production `signalingConnect` for `DaemonConfig.signalingConnect`.
|
|
77
|
+
*
|
|
78
|
+
* The returned function performs one full connect attempt. It throws on any failure
|
|
79
|
+
* (no endpoint, no identity, dial failure, auth rejection, challenge failure); the
|
|
80
|
+
* transport SignalingManager catches the throw and schedules a reconnect.
|
|
81
|
+
*/
|
|
82
|
+
export declare function createSignalingConnect(deps: SignalingConnectDeps): () => Promise<ConnectResult>;
|
|
83
|
+
//# sourceMappingURL=signaling-connect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signaling-connect.d.ts","sourceRoot":"","sources":["../src/signaling-connect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAMH,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,aAAa,EAElB,KAAK,2BAA2B,EACjC,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAqCzC,0FAA0F;AAC1F,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,sGAAsG;IACtG,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,+EAA+E;AAC/E,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,WAAW,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,oBAAoB,EAAE,MAAM,iBAAiB,GAAG,IAAI,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IACzF,4FAA4F;IAC5F,eAAe,EAAE,MAAM,qBAAqB,GAAG,IAAI,CAAC;IACpD,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,iBAAiB,CAAC,EAAE,2BAA2B,CAAC;IAChD,oGAAoG;IACpG,kBAAkB,CAAC,EAAE,MAAM,MAAM,CAAC;IAClC;;;OAGG;IACH,mBAAmB,CAAC,EAAE,CAAC,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACvE;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,KAAK,IAAI,CAAC;CAChD;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,oBAAoB,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,CA2I/F"}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M7 Keystone (Part 2) — production `signalingConnect` for the daemon.
|
|
3
|
+
*
|
|
4
|
+
* This is the daemon's directory-facing dialer. It establishes ONE authenticated
|
|
5
|
+
* signaling stream to a directory node and returns it as a transport
|
|
6
|
+
* `ConnectResult`. The transport `SignalingManager` owns everything after connect:
|
|
7
|
+
* heartbeat, reconnect (it calls connect() again on a fresh attempt), the outbound
|
|
8
|
+
* queue, and optional manifest polling.
|
|
9
|
+
*
|
|
10
|
+
* It is a faithful port of the proven M6 client handshake in
|
|
11
|
+
* `core/client/src/signaling-manager.ts` (`#doOpen`, ~lines 527-643). That path
|
|
12
|
+
* connected the M6 client to the real directory, ran the full DKG ceremony, and
|
|
13
|
+
* sealed sessions — so the 7-step handshake here is the known-good one, not new
|
|
14
|
+
* protocol.
|
|
15
|
+
*
|
|
16
|
+
* Architecture note (2026-06-11 daemon-transport doc): this is the *directory-facing
|
|
17
|
+
* node* — one per daemon, signaling only. A FRESH libp2p node (and therefore a fresh
|
|
18
|
+
* transport key / Peer ID) is created on every connect, which is exactly the
|
|
19
|
+
* reconnect-rotation behavior the architecture calls for. Per-session data exchange
|
|
20
|
+
* uses separate ephemeral session nodes, not this node.
|
|
21
|
+
*
|
|
22
|
+
* Step-5/6 directory identity verification (the consortium-manifest "directory proves
|
|
23
|
+
* itself back" hardening) is OPTIONAL here: it runs only when a `challengeVerifier`
|
|
24
|
+
* is supplied. M6 ran with it off (challengeVerifier = null) and connected fine; the
|
|
25
|
+
* hardening layers on later without changing this path.
|
|
26
|
+
*
|
|
27
|
+
* Crypto reference: agent→directory auth signs SHA-256(domain ‖ nonce ‖ pubkey) with
|
|
28
|
+
* the agent's K_local Ed25519 key (RFC 8032). K_local never leaves the KeyProvider.
|
|
29
|
+
*/
|
|
30
|
+
import { createHash } from "node:crypto";
|
|
31
|
+
import { Encoder, decode } from "cbor-x";
|
|
32
|
+
import * as lp from "it-length-prefixed";
|
|
33
|
+
import { createNode, buildStep5Tbs, } from "@cello-protocol/transport";
|
|
34
|
+
const SIGNALING_PROTOCOL_ID = "/cello/signaling/1.0.0";
|
|
35
|
+
const AUTH_DOMAIN_DIR = "CELLO-DIR-AUTH-v1";
|
|
36
|
+
const AUTH_TIMEOUT_MS = 5_000;
|
|
37
|
+
// tagUint8Array:false — match the M6 client encoder so the directory decodes bytes
|
|
38
|
+
// fields (pubkey, signature, nonce) as raw byte strings, not CBOR-tagged values.
|
|
39
|
+
const CBOR_ENC = new Encoder({ tagUint8Array: false });
|
|
40
|
+
function toU8(v) {
|
|
41
|
+
if (v instanceof Uint8Array)
|
|
42
|
+
return v;
|
|
43
|
+
if (Buffer.isBuffer(v))
|
|
44
|
+
return new Uint8Array(v);
|
|
45
|
+
if (typeof v.slice === "function") {
|
|
46
|
+
return v.slice();
|
|
47
|
+
}
|
|
48
|
+
throw new Error(`expected bytes, got ${typeof v}`);
|
|
49
|
+
}
|
|
50
|
+
function errMsg(err) {
|
|
51
|
+
return err instanceof Error ? err.message : String(err);
|
|
52
|
+
}
|
|
53
|
+
async function nextWithTimeout(iter, timeoutMs) {
|
|
54
|
+
return Promise.race([
|
|
55
|
+
iter.next(),
|
|
56
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("auth_timeout")), timeoutMs)),
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
59
|
+
async function safeStop(node) {
|
|
60
|
+
try {
|
|
61
|
+
await node.stop();
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
/* best-effort teardown */
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Build a production `signalingConnect` for `DaemonConfig.signalingConnect`.
|
|
69
|
+
*
|
|
70
|
+
* The returned function performs one full connect attempt. It throws on any failure
|
|
71
|
+
* (no endpoint, no identity, dial failure, auth rejection, challenge failure); the
|
|
72
|
+
* transport SignalingManager catches the throw and schedules a reconnect.
|
|
73
|
+
*/
|
|
74
|
+
export function createSignalingConnect(deps) {
|
|
75
|
+
return async function connect() {
|
|
76
|
+
const endpoint = await deps.getDirectoryEndpoint();
|
|
77
|
+
if (!endpoint) {
|
|
78
|
+
throw new Error("directory_endpoint_unknown");
|
|
79
|
+
}
|
|
80
|
+
const identity = deps.getAuthIdentity();
|
|
81
|
+
if (!identity) {
|
|
82
|
+
throw new Error("no_agent_identity");
|
|
83
|
+
}
|
|
84
|
+
// Fresh directory-facing node per connect → fresh transport key / Peer ID.
|
|
85
|
+
const node = deps.createDirectoryNode
|
|
86
|
+
? await deps.createDirectoryNode(identity.keyProvider)
|
|
87
|
+
: await createNode({ keyProvider: identity.keyProvider, listenAddresses: ["/ip4/0.0.0.0/tcp/0"] });
|
|
88
|
+
let sigStream;
|
|
89
|
+
try {
|
|
90
|
+
// L1: start() is inside the try so a start failure is caught and the
|
|
91
|
+
// partially-started node is torn down (safeStop) rather than leaked.
|
|
92
|
+
await node.start();
|
|
93
|
+
if (endpoint.multiaddr) {
|
|
94
|
+
// Best-effort dial; newStream below surfaces the real failure if unreachable.
|
|
95
|
+
try {
|
|
96
|
+
await node.dial(endpoint.multiaddr);
|
|
97
|
+
}
|
|
98
|
+
catch (dialErr) {
|
|
99
|
+
// L2: non-fatal (dial-by-peerId may still work / already connected), but
|
|
100
|
+
// keep the real reason — newStream's error is generic.
|
|
101
|
+
deps.logger.debug("directory.dial.failed", {
|
|
102
|
+
multiaddr: endpoint.multiaddr,
|
|
103
|
+
error: errMsg(dialErr),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
sigStream = await node.newStream(endpoint.peerId, SIGNALING_PROTOCOL_ID);
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
await safeStop(node);
|
|
111
|
+
throw new Error(`directory_dial_failed: ${errMsg(err)}`);
|
|
112
|
+
}
|
|
113
|
+
const iter = lp.decode(sigStream)[Symbol.asyncIterator]();
|
|
114
|
+
try {
|
|
115
|
+
// ── Steps 1-2: receive the directory's auth challenge (32-byte nonce) ──
|
|
116
|
+
const challenge = await nextWithTimeout(iter, AUTH_TIMEOUT_MS);
|
|
117
|
+
if (challenge.done || challenge.value === undefined)
|
|
118
|
+
throw new Error("directory_auth_no_challenge");
|
|
119
|
+
const challengeFrame = decode(toU8(challenge.value));
|
|
120
|
+
if (challengeFrame["type"] !== "signaling_auth_challenge") {
|
|
121
|
+
throw new Error(`directory_auth_unexpected_frame: ${String(challengeFrame["type"])}`);
|
|
122
|
+
}
|
|
123
|
+
const nonce = toU8(challengeFrame["nonce"]);
|
|
124
|
+
if (nonce.length !== 32)
|
|
125
|
+
throw new Error("directory_auth_bad_nonce");
|
|
126
|
+
// ── Steps 3-4: sign SHA-256(domain ‖ nonce ‖ pubkey), send auth response ──
|
|
127
|
+
const myPubkey = Buffer.from(identity.pubkeyHex, "hex");
|
|
128
|
+
const domain = Buffer.from(AUTH_DOMAIN_DIR, "utf8");
|
|
129
|
+
const authMsg = new Uint8Array(Buffer.concat([domain, nonce, myPubkey]));
|
|
130
|
+
const msgHash = new Uint8Array(createHash("sha256").update(authMsg).digest());
|
|
131
|
+
const sig = await identity.keyProvider.sign(msgHash);
|
|
132
|
+
const authResponse = CBOR_ENC.encode({
|
|
133
|
+
type: "signaling_auth_response",
|
|
134
|
+
pubkey: myPubkey,
|
|
135
|
+
signature: sig,
|
|
136
|
+
});
|
|
137
|
+
sigStream.send(lp.encode.single(authResponse));
|
|
138
|
+
// ── Step 5: receive signaling_auth_ok ──
|
|
139
|
+
const ack = await nextWithTimeout(iter, AUTH_TIMEOUT_MS);
|
|
140
|
+
if (ack.done || ack.value === undefined)
|
|
141
|
+
throw new Error("directory_auth_no_ack");
|
|
142
|
+
const ackFrame = decode(toU8(ack.value));
|
|
143
|
+
if (ackFrame["type"] !== "signaling_auth_ok") {
|
|
144
|
+
throw new Error(`directory_auth_rejected: ${String(ackFrame["type"])}`);
|
|
145
|
+
}
|
|
146
|
+
// ── Step 6 (optional hardening): verify the directory's identity proof ──
|
|
147
|
+
// Runs only when a challengeVerifier is configured. M6 ran without one.
|
|
148
|
+
let directoryNodeId = typeof ackFrame["nodeId"] === "string" ? ackFrame["nodeId"] : endpoint.peerId;
|
|
149
|
+
const verifier = deps.challengeVerifier;
|
|
150
|
+
if (verifier) {
|
|
151
|
+
const nodeId = ackFrame["nodeId"];
|
|
152
|
+
const signature = ackFrame["signature"];
|
|
153
|
+
const timestamp = ackFrame["timestamp"];
|
|
154
|
+
if (!nodeId || !signature || !timestamp) {
|
|
155
|
+
deps.logger.error("directory.auth.challenge.failed", { reason: "no_identity_proof" });
|
|
156
|
+
throw new Error("directory_challenge_no_identity_proof");
|
|
157
|
+
}
|
|
158
|
+
const tbsBytes = buildStep5Tbs({
|
|
159
|
+
nodeId,
|
|
160
|
+
agentPubkeyHex: identity.pubkeyHex,
|
|
161
|
+
nonceHex: Buffer.from(nonce).toString("hex"),
|
|
162
|
+
isoTimestamp: timestamp,
|
|
163
|
+
});
|
|
164
|
+
const result = verifier.verifyChallenge(nodeId, tbsBytes, signature);
|
|
165
|
+
if (!result.valid) {
|
|
166
|
+
deps.logger.error("directory.auth.challenge.failed", { directoryNodeId: nodeId, reason: result.reason });
|
|
167
|
+
throw new Error(`directory_challenge_failed: ${result.reason}`);
|
|
168
|
+
}
|
|
169
|
+
directoryNodeId = nodeId;
|
|
170
|
+
deps.logger.info("directory.auth.challenge.verified", { directoryNodeId });
|
|
171
|
+
}
|
|
172
|
+
// ── Step 7: announce our peer info so the directory can broker sessions ──
|
|
173
|
+
const peerInfo = CBOR_ENC.encode({
|
|
174
|
+
type: "peer_info_announce",
|
|
175
|
+
peer_id: node.getPeerId(),
|
|
176
|
+
multiaddrs: node.listenAddresses(),
|
|
177
|
+
});
|
|
178
|
+
sigStream.send(lp.encode.single(peerInfo));
|
|
179
|
+
deps.logger.info("directory.signaling.connected", {
|
|
180
|
+
directoryNodeId,
|
|
181
|
+
agentPubkey: identity.pubkeyHex,
|
|
182
|
+
verified: !!verifier,
|
|
183
|
+
});
|
|
184
|
+
// Publish the live, directory-connected node so registration/FROST/seal can
|
|
185
|
+
// open further streams to the directory on the same node.
|
|
186
|
+
deps.publishNode?.(node);
|
|
187
|
+
const stream = wrapSignalingStream(sigStream, iter, node, deps.logger, deps.publishNode);
|
|
188
|
+
return {
|
|
189
|
+
stream,
|
|
190
|
+
directoryNodeId,
|
|
191
|
+
manifestVersion: deps.getManifestVersion?.() ?? 0,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
// Symmetry with publishNode(node): if anything threw after we published (or in a
|
|
196
|
+
// reconnect attempt), clear the daemon's reference so it never points at a node
|
|
197
|
+
// we're about to stop. No-op when nothing was published this attempt.
|
|
198
|
+
deps.publishNode?.(null);
|
|
199
|
+
try {
|
|
200
|
+
sigStream.abort(new Error("directory_auth_error"));
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
/* stream may already be torn down */
|
|
204
|
+
}
|
|
205
|
+
await safeStop(node);
|
|
206
|
+
throw err instanceof Error ? err : new Error(errMsg(err));
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Adapt the post-handshake libp2p stream to the transport `SignalingStream`
|
|
212
|
+
* interface the SignalingManager drives. Frames are CBOR objects; the wire is
|
|
213
|
+
* length-prefixed CBOR, matching the directory and the M6 client exactly.
|
|
214
|
+
*
|
|
215
|
+
* The read loop continues from the SAME iterator the handshake used, so no inbound
|
|
216
|
+
* frame is dropped between auth_ok and the manager registering its handler.
|
|
217
|
+
*/
|
|
218
|
+
function wrapSignalingStream(sigStream, iter, node, logger, publishNode) {
|
|
219
|
+
let closed = false;
|
|
220
|
+
return {
|
|
221
|
+
async send(frame) {
|
|
222
|
+
const encoded = CBOR_ENC.encode(frame);
|
|
223
|
+
sigStream.send(lp.encode.single(encoded));
|
|
224
|
+
},
|
|
225
|
+
onMessage(handler) {
|
|
226
|
+
void (async () => {
|
|
227
|
+
try {
|
|
228
|
+
for (;;) {
|
|
229
|
+
const { value, done } = await iter.next();
|
|
230
|
+
if (done || value === undefined)
|
|
231
|
+
break;
|
|
232
|
+
let frame;
|
|
233
|
+
try {
|
|
234
|
+
frame = decode(toU8(value));
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
// Skip undecodable frames rather than killing the whole stream.
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
handler(frame);
|
|
241
|
+
}
|
|
242
|
+
logger.warn("directory.signaling.stream.ended", {});
|
|
243
|
+
}
|
|
244
|
+
catch (err) {
|
|
245
|
+
logger.warn("directory.signaling.reader.error", { error: errMsg(err) });
|
|
246
|
+
}
|
|
247
|
+
})();
|
|
248
|
+
},
|
|
249
|
+
close() {
|
|
250
|
+
if (closed)
|
|
251
|
+
return;
|
|
252
|
+
closed = true;
|
|
253
|
+
// Clear the daemon's reference before tearing the node down — registration/
|
|
254
|
+
// FROST/seal must not use a node that is being stopped.
|
|
255
|
+
publishNode?.(null);
|
|
256
|
+
try {
|
|
257
|
+
sigStream.abort(new Error("signaling_closed"));
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
/* already closed */
|
|
261
|
+
}
|
|
262
|
+
void safeStop(node);
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
//# sourceMappingURL=signaling-connect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signaling-connect.js","sourceRoot":"","sources":["../src/signaling-connect.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOD-LOG-1 (PERSIST-LOG-001) — at-rest encryption for the durable transcript store.
|
|
3
|
+
*
|
|
4
|
+
* The daemon uses plain node:sqlite (no whole-DB encryption — SQLCipher is a native dep that
|
|
5
|
+
* compiles from source and is intentionally not dragged into the daemon). So the readable
|
|
6
|
+
* transcript plaintext is envelope-encrypted at the column level before it touches disk: each
|
|
7
|
+
* message blob is AES-256-GCM sealed with a dedicated per-DB transcript key.
|
|
8
|
+
*
|
|
9
|
+
* Key separation by design: the agent's Ed25519 identity key SIGNS (the KeyProvider interface is
|
|
10
|
+
* sign-only and never exposes the seed — good hygiene); a SEPARATE 32-byte symmetric key encrypts
|
|
11
|
+
* the transcript at rest. The key lives in a 0600 file beside the daemon DB and is generated once.
|
|
12
|
+
*
|
|
13
|
+
* Blob layout: iv(12) || ciphertext || authTag(16). GCM authenticates, so a tampered or truncated
|
|
14
|
+
* blob fails to decrypt (returns null) rather than yielding garbage plaintext.
|
|
15
|
+
*/
|
|
16
|
+
export declare class TranscriptCipher {
|
|
17
|
+
#private;
|
|
18
|
+
private constructor();
|
|
19
|
+
/**
|
|
20
|
+
* Load the transcript key from `keyPath`, or generate + persist a fresh one (0600) on first use.
|
|
21
|
+
* Atomic-ish create: write then the file is the durable key for this DB for all time.
|
|
22
|
+
*/
|
|
23
|
+
static loadOrCreate(keyPath: string): TranscriptCipher;
|
|
24
|
+
/** For tests: an in-memory cipher with a supplied (or random) key. */
|
|
25
|
+
static fromKey(key?: Buffer): TranscriptCipher;
|
|
26
|
+
/** Encrypt plaintext → iv || ciphertext || tag. */
|
|
27
|
+
encrypt(plaintext: Uint8Array): Uint8Array;
|
|
28
|
+
/** Decrypt an iv||ciphertext||tag blob. Returns null on any tamper / wrong key / malformed input. */
|
|
29
|
+
decrypt(blob: Uint8Array): Uint8Array | null;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=transcript-cipher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript-cipher.d.ts","sourceRoot":"","sources":["../src/transcript-cipher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAUH,qBAAa,gBAAgB;;IAG3B,OAAO;IAIP;;;OAGG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB;IActD,sEAAsE;IACtE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,gBAAgB;IAI9C,mDAAmD;IACnD,OAAO,CAAC,SAAS,EAAE,UAAU,GAAG,UAAU;IAQ1C,qGAAqG;IACrG,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,GAAG,IAAI;CAc7C"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOD-LOG-1 (PERSIST-LOG-001) — at-rest encryption for the durable transcript store.
|
|
3
|
+
*
|
|
4
|
+
* The daemon uses plain node:sqlite (no whole-DB encryption — SQLCipher is a native dep that
|
|
5
|
+
* compiles from source and is intentionally not dragged into the daemon). So the readable
|
|
6
|
+
* transcript plaintext is envelope-encrypted at the column level before it touches disk: each
|
|
7
|
+
* message blob is AES-256-GCM sealed with a dedicated per-DB transcript key.
|
|
8
|
+
*
|
|
9
|
+
* Key separation by design: the agent's Ed25519 identity key SIGNS (the KeyProvider interface is
|
|
10
|
+
* sign-only and never exposes the seed — good hygiene); a SEPARATE 32-byte symmetric key encrypts
|
|
11
|
+
* the transcript at rest. The key lives in a 0600 file beside the daemon DB and is generated once.
|
|
12
|
+
*
|
|
13
|
+
* Blob layout: iv(12) || ciphertext || authTag(16). GCM authenticates, so a tampered or truncated
|
|
14
|
+
* blob fails to decrypt (returns null) rather than yielding garbage plaintext.
|
|
15
|
+
*/
|
|
16
|
+
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
|
|
17
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
18
|
+
import { dirname } from "node:path";
|
|
19
|
+
const KEY_LEN = 32; // AES-256
|
|
20
|
+
const IV_LEN = 12; // GCM standard nonce
|
|
21
|
+
const TAG_LEN = 16;
|
|
22
|
+
export class TranscriptCipher {
|
|
23
|
+
#key;
|
|
24
|
+
constructor(key) {
|
|
25
|
+
this.#key = key;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Load the transcript key from `keyPath`, or generate + persist a fresh one (0600) on first use.
|
|
29
|
+
* Atomic-ish create: write then the file is the durable key for this DB for all time.
|
|
30
|
+
*/
|
|
31
|
+
static loadOrCreate(keyPath) {
|
|
32
|
+
if (existsSync(keyPath)) {
|
|
33
|
+
const key = readFileSync(keyPath);
|
|
34
|
+
if (key.length !== KEY_LEN) {
|
|
35
|
+
throw new Error(`transcript key at ${keyPath} is ${key.length} bytes, expected ${KEY_LEN}`);
|
|
36
|
+
}
|
|
37
|
+
return new TranscriptCipher(key);
|
|
38
|
+
}
|
|
39
|
+
const key = randomBytes(KEY_LEN);
|
|
40
|
+
mkdirSync(dirname(keyPath), { recursive: true });
|
|
41
|
+
writeFileSync(keyPath, key, { mode: 0o600 });
|
|
42
|
+
return new TranscriptCipher(key);
|
|
43
|
+
}
|
|
44
|
+
/** For tests: an in-memory cipher with a supplied (or random) key. */
|
|
45
|
+
static fromKey(key) {
|
|
46
|
+
return new TranscriptCipher(key ?? randomBytes(KEY_LEN));
|
|
47
|
+
}
|
|
48
|
+
/** Encrypt plaintext → iv || ciphertext || tag. */
|
|
49
|
+
encrypt(plaintext) {
|
|
50
|
+
const iv = randomBytes(IV_LEN);
|
|
51
|
+
const cipher = createCipheriv("aes-256-gcm", this.#key, iv);
|
|
52
|
+
const ct = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
53
|
+
const tag = cipher.getAuthTag();
|
|
54
|
+
return Buffer.concat([iv, ct, tag]);
|
|
55
|
+
}
|
|
56
|
+
/** Decrypt an iv||ciphertext||tag blob. Returns null on any tamper / wrong key / malformed input. */
|
|
57
|
+
decrypt(blob) {
|
|
58
|
+
if (blob.length < IV_LEN + TAG_LEN)
|
|
59
|
+
return null;
|
|
60
|
+
const buf = Buffer.from(blob);
|
|
61
|
+
const iv = buf.subarray(0, IV_LEN);
|
|
62
|
+
const tag = buf.subarray(buf.length - TAG_LEN);
|
|
63
|
+
const ct = buf.subarray(IV_LEN, buf.length - TAG_LEN);
|
|
64
|
+
try {
|
|
65
|
+
const decipher = createDecipheriv("aes-256-gcm", this.#key, iv);
|
|
66
|
+
decipher.setAuthTag(tag);
|
|
67
|
+
return Uint8Array.from(Buffer.concat([decipher.update(ct), decipher.final()]));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=transcript-cipher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript-cipher.js","sourceRoot":"","sources":["../src/transcript-cipher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CELLO Daemon — transport-composition.ts (CELLO-M7-TRANSPORT-001)
|
|
3
|
+
*
|
|
4
|
+
* Composition-root selection of the transport adapters by CELLO_ENV. Per CLAUDE.md
|
|
5
|
+
* the daemon composition root instantiates all adapters and fails fast at startup
|
|
6
|
+
* (not at first session) when a production environment is missing required config.
|
|
7
|
+
*
|
|
8
|
+
* 'local' | 'test' → in-process stubs (no network).
|
|
9
|
+
* 'dev' | 'staging' | 'production' → real adapters; require their backing
|
|
10
|
+
* dependencies (a TransportDialer for the
|
|
11
|
+
* selector). Missing config → throw at
|
|
12
|
+
* startup naming what is missing (AC-010).
|
|
13
|
+
*/
|
|
14
|
+
import { type ITransportSelector, type TransportDialer } from "./transport-selector.js";
|
|
15
|
+
import type { Logger } from "./types.js";
|
|
16
|
+
export type CelloEnv = "local" | "test" | "dev" | "staging" | "production";
|
|
17
|
+
/** Resolve CELLO_ENV. Unknown/undefined defaults to 'local' (in-process stubs). */
|
|
18
|
+
export declare function resolveCelloEnv(raw: string | undefined): CelloEnv;
|
|
19
|
+
/** True for environments that require real (network-backed) transport adapters. */
|
|
20
|
+
export declare function isProductionVariant(env: CelloEnv): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Build the transport selector for the given environment. Stub for local/test;
|
|
23
|
+
* real TransportSelector for production variants (requires a TransportDialer).
|
|
24
|
+
*/
|
|
25
|
+
export declare function createTransportSelector(opts: {
|
|
26
|
+
env: CelloEnv;
|
|
27
|
+
logger: Logger;
|
|
28
|
+
transportDialer?: TransportDialer;
|
|
29
|
+
directDialTimeoutMs?: number;
|
|
30
|
+
}): ITransportSelector;
|
|
31
|
+
//# sourceMappingURL=transport-composition.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport-composition.d.ts","sourceRoot":"","sources":["../src/transport-composition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACrB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,SAAS,GAAG,YAAY,CAAC;AAE3E,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,CAWjE;AAED,mFAAmF;AACnF,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAE1D;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAC5C,GAAG,EAAE,QAAQ,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B,GAAG,kBAAkB,CAcrB"}
|