@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,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-signing FROST ceremony participation — daemon side (DOD-SPINE-5, increment 3).
|
|
3
|
+
*
|
|
4
|
+
* When an initiator's `cello_initiate_session` reaches the directory, the directory
|
|
5
|
+
* FROST-signs the SessionAssignment by DELEGATING to the initiator: its
|
|
6
|
+
* `ClientDelegatedSigner.participateInCeremony` sends a `ceremony_request`
|
|
7
|
+
* {ceremony_id, tbs, context} over the initiator's authenticated signaling stream and
|
|
8
|
+
* awaits a `ceremony_result` {ceremony_id, signature}. The initiator (coordinator) runs
|
|
9
|
+
* the FROST ceremony with its share + the directory's K_server_X shares, and replies.
|
|
10
|
+
*
|
|
11
|
+
* This wires that handler onto a PER-AGENT signaling stream (the same per-agent routing
|
|
12
|
+
* SPINE-4 established for registration). It is a faithful port of
|
|
13
|
+
* core/client `SealManager.handleCeremonyRequest` + `client-startup` signer
|
|
14
|
+
* reconstruction — the daemon must NOT import the dead core/client stack.
|
|
15
|
+
*
|
|
16
|
+
* The threshold signer is reconstructed lazily from the agent's persisted
|
|
17
|
+
* `frost-share.json` (the daemon holds no FrostThresholdSigner after registration —
|
|
18
|
+
* the registration-time signer was on the disposed RegistrationContext).
|
|
19
|
+
*/
|
|
20
|
+
import { type LegibilityForHash } from "./seal-legibility-tbs.js";
|
|
21
|
+
import type { IThresholdSigner } from "@cello-protocol/crypto";
|
|
22
|
+
import type { CelloNode } from "@cello-protocol/transport";
|
|
23
|
+
import type { SignalingSeam } from "./registration-context.js";
|
|
24
|
+
import type { Logger } from "./types.js";
|
|
25
|
+
/**
|
|
26
|
+
* WIRE-002: answer the directory's `session_offer` on a per-agent signaling stream. When an
|
|
27
|
+
* initiator's session_request names this agent as the target, the directory sends a
|
|
28
|
+
* `session_offer {session_id}` before it builds the FROST-signed assignment; the agent must
|
|
29
|
+
* reply `session_offer_accept` advertising its SESSION endpoint (its standing receiver, which
|
|
30
|
+
* `acceptSession` reuses as the receiver-side session node) so the directory can fold the
|
|
31
|
+
* counterparty endpoint into the assignment and the initiator can reach it. Without this, the
|
|
32
|
+
* assignment carries an empty counterparty endpoint and content delivery can never connect.
|
|
33
|
+
*/
|
|
34
|
+
export declare function wireSessionOfferHandler(deps: {
|
|
35
|
+
agentName: string;
|
|
36
|
+
getStandingReceiverEndpoint: () => {
|
|
37
|
+
peerId: string;
|
|
38
|
+
addrs: string[];
|
|
39
|
+
} | null;
|
|
40
|
+
signaling: SignalingSeam;
|
|
41
|
+
logger: Logger;
|
|
42
|
+
}): () => void;
|
|
43
|
+
export interface CeremonyWiringDeps {
|
|
44
|
+
agentName: string;
|
|
45
|
+
/** `${celloDir}/agents/<name>` — holds frost-share.json. */
|
|
46
|
+
agentDir: string;
|
|
47
|
+
agentPubkeyHex: string;
|
|
48
|
+
/** The agent's directory-connected libp2p node (the per-agent signaling node). */
|
|
49
|
+
getNode: () => CelloNode | null;
|
|
50
|
+
/** Resolve the directory endpoint to open FROST streams on. */
|
|
51
|
+
getDirectoryEndpoint: () => Promise<{
|
|
52
|
+
peerId: string;
|
|
53
|
+
multiaddr?: string;
|
|
54
|
+
} | null>;
|
|
55
|
+
/** The agent's per-agent signaling seam (where ceremony_request arrives + result is sent). */
|
|
56
|
+
signaling: SignalingSeam;
|
|
57
|
+
logger: Logger;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Reconstruct the agent's FROST threshold signer from its persisted share, configured to
|
|
61
|
+
* drive the ceremony's `/cello/frost/1.0.0` round-trips on the agent's directory node.
|
|
62
|
+
* Returns null when no share is persisted or the share is unreadable.
|
|
63
|
+
*/
|
|
64
|
+
export declare function reconstructThresholdSigner(deps: CeremonyWiringDeps): Promise<IThresholdSigner | null>;
|
|
65
|
+
/**
|
|
66
|
+
* Register a `ceremony_request` handler on the agent's signaling seam. On the directory's
|
|
67
|
+
* delegated-signing request, run the agent's threshold signer's participateInCeremony and
|
|
68
|
+
* reply with `ceremony_result`. The signer is reconstructed lazily and cached. Returns the
|
|
69
|
+
* unregister function. SI-001: tbs/signature never logged.
|
|
70
|
+
*/
|
|
71
|
+
/**
|
|
72
|
+
* DOD-SPINE-7: register a `seal_verified` handler on the agent's signaling seam. After the
|
|
73
|
+
* directory rebuilds + verifies the bilateral seal's signed Merkle chain (relay-mediated
|
|
74
|
+
* processSeal FROST path), it sends the SEAL INITIATOR a `seal_verified` {session_id,
|
|
75
|
+
* sealed_root, leaf_count, timestamp} and waits for the initiator to COORDINATE the seal FROST
|
|
76
|
+
* ceremony. This handler reconstructs the agent's threshold signer (same machinery as the
|
|
77
|
+
* session ceremony), runs participateInCeremony over buildSealTbs with context
|
|
78
|
+
* "cello-frost-seal-v1", and replies `seal_frost_signature` {session_id, frost_signature}. The
|
|
79
|
+
* directory's #processSealFrostSignature then completes notarization and delivers
|
|
80
|
+
* `session_sealed` (the byte-identical sealed_root) to both parties.
|
|
81
|
+
*
|
|
82
|
+
* Faithful port of core/client `SealManager.handleSealVerified` (the dead stack); the daemon
|
|
83
|
+
* must NOT import that stack. SI-001: tbs/signature never logged.
|
|
84
|
+
*/
|
|
85
|
+
export declare function wireSealCeremonyHandler(deps: CeremonyWiringDeps): () => void;
|
|
86
|
+
/**
|
|
87
|
+
* SESSION-002 (DOD-SEAL-3): verify a unilateral seal certificate WITHOUT trusting the
|
|
88
|
+
* channel. Rebuilds the canonical seal TBS from the cert fields and verifies the signature
|
|
89
|
+
* against a key trusted independently of the delivering frame: the session primary_pubkey
|
|
90
|
+
* (commitments[0] of this agent's FROST share) for 'frost'. A channel-swapped sealed_root
|
|
91
|
+
* (or any TBS-bound field) fails this check (SI-003). The 'single' (pre-DKG) variant verifies
|
|
92
|
+
* against the directory node key from the consortium manifest — not yet wired on the daemon;
|
|
93
|
+
* surfaced honestly rather than accepted on faith.
|
|
94
|
+
*/
|
|
95
|
+
export declare function verifyUnilateralCertificate(deps: {
|
|
96
|
+
agentDir: string;
|
|
97
|
+
agentPubkeyHex: string;
|
|
98
|
+
logger: Logger;
|
|
99
|
+
}, cert: {
|
|
100
|
+
sessionId: Uint8Array;
|
|
101
|
+
sealedRoot: Uint8Array;
|
|
102
|
+
leafCount: number;
|
|
103
|
+
closeTimestamp: number;
|
|
104
|
+
frostSignature: Uint8Array;
|
|
105
|
+
signatureType: "frost" | "single";
|
|
106
|
+
}): Promise<{
|
|
107
|
+
ok: true;
|
|
108
|
+
} | {
|
|
109
|
+
ok: false;
|
|
110
|
+
reason: string;
|
|
111
|
+
}>;
|
|
112
|
+
/**
|
|
113
|
+
* M7 legibility-TBS-binding: verify a BILATERAL session_sealed certificate's signature over the
|
|
114
|
+
* legibility-bound TBS, channel-independently.
|
|
115
|
+
*
|
|
116
|
+
* KEY STRUCTURE (why this is asymmetric): the seal's FROST signature is produced by the
|
|
117
|
+
* INITIATOR's group key (commitments[0] of the initiator's DKG share) + the directory's K_server
|
|
118
|
+
* shares. So only the INITIATOR holds the verification key locally — it loads its OWN primary from
|
|
119
|
+
* its share and checks `signer_pubkey === own primary`, then verifies the FROST signature over
|
|
120
|
+
* `buildSealTbs ‖ legibilityHash`. A tampered legibility (answered / content_frontier_seq /
|
|
121
|
+
* attestation_mode — carried unsigned on the frame) changes the hash → the signature fails → REJECT.
|
|
122
|
+
*
|
|
123
|
+
* The NON-INITIATOR (the responder B) does NOT hold the initiator's group key, so it cannot
|
|
124
|
+
* channel-independently verify here — it ACCEPTS (`verified:false`). This is sound for the LIVE
|
|
125
|
+
* path: session_sealed arrives over the daemon↔directory libp2p Noise channel (authenticated +
|
|
126
|
+
* encrypted), so it is not MITM-tamperable in transit. The binding's primary value is OUT-OF-BAND:
|
|
127
|
+
* any holder of the initiator's primary (an arbitrator) can verify an exported cert's legibility.
|
|
128
|
+
* Giving the responder the initiator's primary via the FROST-signed session establishment (so it,
|
|
129
|
+
* too, can verify locally) is a follow-on.
|
|
130
|
+
*
|
|
131
|
+
* `legibility` MUST be the AS-RECEIVED wire object (not a normalised copy) — the directory signed
|
|
132
|
+
* over the canonical hash of exactly what it sent.
|
|
133
|
+
*/
|
|
134
|
+
export declare function verifyBilateralSealCertificate(deps: {
|
|
135
|
+
agentDir: string;
|
|
136
|
+
agentPubkeyHex: string;
|
|
137
|
+
logger: Logger;
|
|
138
|
+
counterpartyPrimaryHex?: string | null;
|
|
139
|
+
}, cert: {
|
|
140
|
+
sessionId: Uint8Array;
|
|
141
|
+
sealedRoot: Uint8Array;
|
|
142
|
+
leafCount: number;
|
|
143
|
+
closeTimestamp: number;
|
|
144
|
+
frostSignature: Uint8Array;
|
|
145
|
+
signerPubkey: Uint8Array;
|
|
146
|
+
signatureType: "frost" | "single";
|
|
147
|
+
legibility: LegibilityForHash | null;
|
|
148
|
+
}): Promise<{
|
|
149
|
+
ok: true;
|
|
150
|
+
verified: boolean;
|
|
151
|
+
} | {
|
|
152
|
+
ok: false;
|
|
153
|
+
reason: string;
|
|
154
|
+
}>;
|
|
155
|
+
export declare function wireSessionCeremonyHandler(deps: CeremonyWiringDeps): () => void;
|
|
156
|
+
//# sourceMappingURL=session-ceremony.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-ceremony.d.ts","sourceRoot":"","sources":["../src/session-ceremony.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-signing FROST ceremony participation — daemon side (DOD-SPINE-5, increment 3).
|
|
3
|
+
*
|
|
4
|
+
* When an initiator's `cello_initiate_session` reaches the directory, the directory
|
|
5
|
+
* FROST-signs the SessionAssignment by DELEGATING to the initiator: its
|
|
6
|
+
* `ClientDelegatedSigner.participateInCeremony` sends a `ceremony_request`
|
|
7
|
+
* {ceremony_id, tbs, context} over the initiator's authenticated signaling stream and
|
|
8
|
+
* awaits a `ceremony_result` {ceremony_id, signature}. The initiator (coordinator) runs
|
|
9
|
+
* the FROST ceremony with its share + the directory's K_server_X shares, and replies.
|
|
10
|
+
*
|
|
11
|
+
* This wires that handler onto a PER-AGENT signaling stream (the same per-agent routing
|
|
12
|
+
* SPINE-4 established for registration). It is a faithful port of
|
|
13
|
+
* core/client `SealManager.handleCeremonyRequest` + `client-startup` signer
|
|
14
|
+
* reconstruction — the daemon must NOT import the dead core/client stack.
|
|
15
|
+
*
|
|
16
|
+
* The threshold signer is reconstructed lazily from the agent's persisted
|
|
17
|
+
* `frost-share.json` (the daemon holds no FrostThresholdSigner after registration —
|
|
18
|
+
* the registration-time signer was on the disposed RegistrationContext).
|
|
19
|
+
*/
|
|
20
|
+
import { decode } from "cbor-x";
|
|
21
|
+
import { buildSealTbs } from "@cello-protocol/protocol-types";
|
|
22
|
+
import { bindLegibilityToTbs } from "./seal-legibility-tbs.js";
|
|
23
|
+
import { reDeriveFrontiers, findInflatedFrontier } from "./seal-frontier-verify.js";
|
|
24
|
+
import { FrostThresholdSigner } from "@cello-protocol/crypto";
|
|
25
|
+
import { storeDkgResult } from "@cello-protocol/crypto/frost/frost-threshold-signer.js";
|
|
26
|
+
import { NetworkDirectoryNode } from "./network-directory-node.js";
|
|
27
|
+
import { FileRegistrationPersistence } from "./registration-persistence.js";
|
|
28
|
+
/**
|
|
29
|
+
* WIRE-002: answer the directory's `session_offer` on a per-agent signaling stream. When an
|
|
30
|
+
* initiator's session_request names this agent as the target, the directory sends a
|
|
31
|
+
* `session_offer {session_id}` before it builds the FROST-signed assignment; the agent must
|
|
32
|
+
* reply `session_offer_accept` advertising its SESSION endpoint (its standing receiver, which
|
|
33
|
+
* `acceptSession` reuses as the receiver-side session node) so the directory can fold the
|
|
34
|
+
* counterparty endpoint into the assignment and the initiator can reach it. Without this, the
|
|
35
|
+
* assignment carries an empty counterparty endpoint and content delivery can never connect.
|
|
36
|
+
*/
|
|
37
|
+
export function wireSessionOfferHandler(deps) {
|
|
38
|
+
return deps.signaling.registerInboundHandler((frame) => {
|
|
39
|
+
if (frame["type"] !== "session_offer")
|
|
40
|
+
return;
|
|
41
|
+
void (async () => {
|
|
42
|
+
const sidRaw = frame["session_id"];
|
|
43
|
+
const sessionId = sidRaw instanceof Uint8Array ? sidRaw : Buffer.isBuffer(sidRaw) ? new Uint8Array(sidRaw) : null;
|
|
44
|
+
if (!sessionId) {
|
|
45
|
+
deps.logger.warn("session.offer.abort", { agentName: deps.agentName, reason: "no_session_id" });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const sr = deps.getStandingReceiverEndpoint();
|
|
49
|
+
if (!sr) {
|
|
50
|
+
deps.logger.warn("session.offer.abort", { agentName: deps.agentName, reason: "standing_receiver_unavailable" });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
await deps.signaling.sendRaw({
|
|
55
|
+
type: "session_offer_accept",
|
|
56
|
+
session_id: sessionId,
|
|
57
|
+
counterparty_session_peer_id: sr.peerId,
|
|
58
|
+
counterparty_session_addrs: sr.addrs,
|
|
59
|
+
});
|
|
60
|
+
deps.logger.info("session.offer.accepted", {
|
|
61
|
+
agentName: deps.agentName,
|
|
62
|
+
sessionPeerId: sr.peerId,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
deps.logger.warn("session.offer.accept.failed", {
|
|
67
|
+
agentName: deps.agentName,
|
|
68
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
})();
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Reconstruct the agent's FROST threshold signer from its persisted share, configured to
|
|
76
|
+
* drive the ceremony's `/cello/frost/1.0.0` round-trips on the agent's directory node.
|
|
77
|
+
* Returns null when no share is persisted or the share is unreadable.
|
|
78
|
+
*/
|
|
79
|
+
export async function reconstructThresholdSigner(deps) {
|
|
80
|
+
const persistence = new FileRegistrationPersistence({ agentDir: deps.agentDir, logger: deps.logger });
|
|
81
|
+
const share = await persistence.loadActiveFrostKeyShare();
|
|
82
|
+
if (!share)
|
|
83
|
+
return null;
|
|
84
|
+
const frostSecret = { identifier: share.identifier, signingShare: new Uint8Array(share.signingShare) };
|
|
85
|
+
let commitments = [];
|
|
86
|
+
const verifyingShares = {};
|
|
87
|
+
try {
|
|
88
|
+
const dc = decode(Buffer.from(share.commitmentsCbor));
|
|
89
|
+
if (Array.isArray(dc)) {
|
|
90
|
+
commitments = dc.map((c) => c instanceof Uint8Array ? c : Buffer.isBuffer(c) ? new Uint8Array(c) : new Uint8Array(0));
|
|
91
|
+
}
|
|
92
|
+
const dvs = decode(Buffer.from(share.verifyingSharesCbor));
|
|
93
|
+
if (dvs && typeof dvs === "object" && !Array.isArray(dvs)) {
|
|
94
|
+
for (const [k, v] of Object.entries(dvs)) {
|
|
95
|
+
verifyingShares[k] = v instanceof Uint8Array ? v : Buffer.isBuffer(v) ? new Uint8Array(v) : new Uint8Array(0);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
deps.logger.error("session.ceremony.share.load.failed", {
|
|
101
|
+
agentName: deps.agentName,
|
|
102
|
+
reason: "cbor_deserialize_failed",
|
|
103
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
104
|
+
});
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const frostPublic = { signers: { min: share.threshold, max: share.participants + 1 }, commitments, verifyingShares };
|
|
108
|
+
try {
|
|
109
|
+
// SI-001: storeDkgResult does not log the secret. Cast via the function's own param
|
|
110
|
+
// types (the daemon has no direct @noble/curves dependency).
|
|
111
|
+
storeDkgResult(deps.agentPubkeyHex, frostSecret, frostPublic);
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
deps.logger.error("session.ceremony.share.load.failed", {
|
|
115
|
+
agentName: deps.agentName,
|
|
116
|
+
reason: "storeDkgResult_failed",
|
|
117
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
118
|
+
});
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
let directoryNodeStubs;
|
|
122
|
+
const ep = await deps.getDirectoryEndpoint();
|
|
123
|
+
const node = deps.getNode();
|
|
124
|
+
if (ep && ep.multiaddr && node) {
|
|
125
|
+
const stub = new NetworkDirectoryNode({
|
|
126
|
+
id: ep.peerId,
|
|
127
|
+
node,
|
|
128
|
+
directoryPeerId: ep.peerId,
|
|
129
|
+
directoryMultiaddrs: [ep.multiaddr],
|
|
130
|
+
logger: deps.logger,
|
|
131
|
+
});
|
|
132
|
+
stub.setBootstrapContext(deps.agentPubkeyHex, `${deps.agentPubkeyHex}:epoch:1`);
|
|
133
|
+
directoryNodeStubs = [stub];
|
|
134
|
+
}
|
|
135
|
+
return new FrostThresholdSigner({ threshold: share.threshold, participants: share.participants, directoryNodeStubs }, Buffer.from(deps.agentPubkeyHex, "hex"));
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Register a `ceremony_request` handler on the agent's signaling seam. On the directory's
|
|
139
|
+
* delegated-signing request, run the agent's threshold signer's participateInCeremony and
|
|
140
|
+
* reply with `ceremony_result`. The signer is reconstructed lazily and cached. Returns the
|
|
141
|
+
* unregister function. SI-001: tbs/signature never logged.
|
|
142
|
+
*/
|
|
143
|
+
/**
|
|
144
|
+
* DOD-SPINE-7: register a `seal_verified` handler on the agent's signaling seam. After the
|
|
145
|
+
* directory rebuilds + verifies the bilateral seal's signed Merkle chain (relay-mediated
|
|
146
|
+
* processSeal FROST path), it sends the SEAL INITIATOR a `seal_verified` {session_id,
|
|
147
|
+
* sealed_root, leaf_count, timestamp} and waits for the initiator to COORDINATE the seal FROST
|
|
148
|
+
* ceremony. This handler reconstructs the agent's threshold signer (same machinery as the
|
|
149
|
+
* session ceremony), runs participateInCeremony over buildSealTbs with context
|
|
150
|
+
* "cello-frost-seal-v1", and replies `seal_frost_signature` {session_id, frost_signature}. The
|
|
151
|
+
* directory's #processSealFrostSignature then completes notarization and delivers
|
|
152
|
+
* `session_sealed` (the byte-identical sealed_root) to both parties.
|
|
153
|
+
*
|
|
154
|
+
* Faithful port of core/client `SealManager.handleSealVerified` (the dead stack); the daemon
|
|
155
|
+
* must NOT import that stack. SI-001: tbs/signature never logged.
|
|
156
|
+
*/
|
|
157
|
+
export function wireSealCeremonyHandler(deps) {
|
|
158
|
+
return deps.signaling.registerInboundHandler((frame) => {
|
|
159
|
+
if (frame["type"] !== "seal_verified")
|
|
160
|
+
return;
|
|
161
|
+
void (async () => {
|
|
162
|
+
const toU8 = (v) => v instanceof Uint8Array ? v : Buffer.isBuffer(v) ? new Uint8Array(v) : null;
|
|
163
|
+
const sessionId = toU8(frame["session_id"]);
|
|
164
|
+
const sealedRoot = toU8(frame["sealed_root"]);
|
|
165
|
+
const leafCountRaw = frame["leaf_count"];
|
|
166
|
+
const leafCount = typeof leafCountRaw === "number" ? leafCountRaw : null;
|
|
167
|
+
const tsRaw = frame["timestamp"];
|
|
168
|
+
const timestamp = typeof tsRaw === "number" ? tsRaw : typeof tsRaw === "bigint" ? Number(tsRaw) : null;
|
|
169
|
+
if (!sessionId || !sealedRoot || leafCount === null || timestamp === null) {
|
|
170
|
+
deps.logger.warn("session.seal.ceremony.abort", {
|
|
171
|
+
agentName: deps.agentName,
|
|
172
|
+
reason: !sessionId ? "no_session_id" : !sealedRoot ? "no_sealed_root" : leafCount === null ? "no_leaf_count" : "no_timestamp",
|
|
173
|
+
});
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const sidHex = Buffer.from(sessionId).toString("hex");
|
|
177
|
+
// Reconstruct a FRESH signer per ceremony (same rationale as the session ceremony:
|
|
178
|
+
// fresh directory endpoint, no shared FROST state across concurrent ceremonies).
|
|
179
|
+
const signer = await reconstructThresholdSigner(deps);
|
|
180
|
+
if (!signer) {
|
|
181
|
+
deps.logger.warn("session.seal.ceremony.abort", { agentName: deps.agentName, sessionId: sidHex, reason: "no_signer" });
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
// M7 legibility-TBS-binding: when the directory's seal_verified carries `legibility` (the
|
|
185
|
+
// bilateral seal), fold its canonical hash into the TBS we co-sign — so the FROST signature
|
|
186
|
+
// covers the legibility and a MITM cannot tamper answered/frontier/attestation_mode in
|
|
187
|
+
// transit. The directory binds the IDENTICAL hash (it built the TBS it verifies against);
|
|
188
|
+
// the receiving client re-binds and verifies. Unilateral seal_verified carries no legibility
|
|
189
|
+
// → plain TBS (bindLegibilityToTbs is a no-op), matching the directory's unilateral TBS.
|
|
190
|
+
const legibility = frame["legibility"];
|
|
191
|
+
const legForHash = legibility && typeof legibility === "object" ? legibility : null;
|
|
192
|
+
// DOD-LEG-2 (SI-002): the INITIATOR must not co-sign an inflated certificate — otherwise a
|
|
193
|
+
// validly-FROST-signed inflated cert would exist as a durable artifact. Re-derive each party's
|
|
194
|
+
// content_frontier_seq from the signed leaves the directory ships on seal_verified and ABORT
|
|
195
|
+
// the ceremony (no signature) if any published frontier is inflated. Same logic the receiver
|
|
196
|
+
// applies at session_sealed; doing it here means a lying directory gets NO signature at all.
|
|
197
|
+
const wireParts = legForHash && Array.isArray(legForHash.participants)
|
|
198
|
+
? (legForHash.participants)
|
|
199
|
+
: null;
|
|
200
|
+
if (wireParts) {
|
|
201
|
+
// Wire pubkeys are Uint8Array; normalise to hex for comparison with the re-derived map.
|
|
202
|
+
const parts = wireParts.map((p) => ({
|
|
203
|
+
pubkey: p.pubkey instanceof Uint8Array
|
|
204
|
+
? Buffer.from(p.pubkey).toString("hex")
|
|
205
|
+
: (typeof p.pubkey === "string" ? p.pubkey : null),
|
|
206
|
+
content_frontier_seq: typeof p.content_frontier_seq === "number" ? p.content_frontier_seq : null,
|
|
207
|
+
}));
|
|
208
|
+
const malformed = parts.some((p) => p.pubkey === null || p.content_frontier_seq === null);
|
|
209
|
+
const anyClaimed = parts.some((p) => (p.content_frontier_seq ?? 0) > 0);
|
|
210
|
+
const flRaw = frame["frontier_leaves"];
|
|
211
|
+
const haveLeaves = Array.isArray(flRaw) && flRaw.length > 0;
|
|
212
|
+
if (malformed || (anyClaimed && !haveLeaves)) {
|
|
213
|
+
deps.logger.warn("session.seal.ceremony.abort", {
|
|
214
|
+
agentName: deps.agentName, sessionId: sidHex,
|
|
215
|
+
reason: malformed ? "frontier_participant_malformed" : "frontier_leaves_missing",
|
|
216
|
+
});
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (haveLeaves) {
|
|
220
|
+
const toLeaf = (v) => (v instanceof Uint8Array ? v : new Uint8Array(v));
|
|
221
|
+
const leaves = flRaw.map((l) => {
|
|
222
|
+
const o = l;
|
|
223
|
+
return { structure1_cbor: toLeaf(o["structure1_cbor"]), sender_pubkey: toLeaf(o["sender_pubkey"]), sender_signature: toLeaf(o["sender_signature"]) };
|
|
224
|
+
});
|
|
225
|
+
const rederived = reDeriveFrontiers(leaves, sessionId);
|
|
226
|
+
const inflated = rederived.ok
|
|
227
|
+
? findInflatedFrontier(parts, rederived.frontiers)
|
|
228
|
+
: null;
|
|
229
|
+
if (!rederived.ok || inflated) {
|
|
230
|
+
deps.logger.warn("session.seal.ceremony.abort", {
|
|
231
|
+
agentName: deps.agentName, sessionId: sidHex,
|
|
232
|
+
reason: rederived.ok ? "frontier_unverifiable" : rederived.reason,
|
|
233
|
+
...(inflated ? { party: inflated.party, publishedFrontier: inflated.publishedFrontier, derivedFrontier: inflated.derivedFrontier } : {}),
|
|
234
|
+
});
|
|
235
|
+
return; // refuse to co-sign — the directory gets no signature for an inflated cert
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const tbs = bindLegibilityToTbs(buildSealTbs(sessionId, sealedRoot, leafCount, timestamp), legForHash);
|
|
240
|
+
let frostSignature = null;
|
|
241
|
+
try {
|
|
242
|
+
// The initiator COORDINATES the seal FROST ceremony (its signer drives the
|
|
243
|
+
// /cello/frost round-trips with the directory's K_server shares via directoryNodeStubs).
|
|
244
|
+
const result = await signer.participateInCeremony(`seal:${sidHex}`, tbs, "cello-frost-seal-v1");
|
|
245
|
+
frostSignature = result.ok ? new Uint8Array(result.signature) : null;
|
|
246
|
+
deps.logger.info("session.seal.ceremony.participated", { agentName: deps.agentName, sessionId: sidHex, ok: result.ok });
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
deps.logger.warn("session.seal.ceremony.failed", {
|
|
250
|
+
agentName: deps.agentName,
|
|
251
|
+
sessionId: sidHex,
|
|
252
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
253
|
+
});
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (!frostSignature)
|
|
257
|
+
return; // ceremony failed (threshold not met) — no signature to send
|
|
258
|
+
try {
|
|
259
|
+
await deps.signaling.sendRaw({ type: "seal_frost_signature", session_id: sessionId, frost_signature: frostSignature });
|
|
260
|
+
deps.logger.info("session.seal.frost.signature.sent", { agentName: deps.agentName, sessionId: sidHex });
|
|
261
|
+
}
|
|
262
|
+
catch (err) {
|
|
263
|
+
deps.logger.warn("session.seal.frost.signature.send.failed", {
|
|
264
|
+
agentName: deps.agentName,
|
|
265
|
+
sessionId: sidHex,
|
|
266
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
})();
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* SESSION-002 (DOD-SEAL-3): verify a unilateral seal certificate WITHOUT trusting the
|
|
274
|
+
* channel. Rebuilds the canonical seal TBS from the cert fields and verifies the signature
|
|
275
|
+
* against a key trusted independently of the delivering frame: the session primary_pubkey
|
|
276
|
+
* (commitments[0] of this agent's FROST share) for 'frost'. A channel-swapped sealed_root
|
|
277
|
+
* (or any TBS-bound field) fails this check (SI-003). The 'single' (pre-DKG) variant verifies
|
|
278
|
+
* against the directory node key from the consortium manifest — not yet wired on the daemon;
|
|
279
|
+
* surfaced honestly rather than accepted on faith.
|
|
280
|
+
*/
|
|
281
|
+
export async function verifyUnilateralCertificate(deps, cert) {
|
|
282
|
+
const tbs = buildSealTbs(cert.sessionId, cert.sealedRoot, cert.leafCount, cert.closeTimestamp);
|
|
283
|
+
if (cert.signatureType !== "frost") {
|
|
284
|
+
// 'single' (pre-DKG) — verify vs the directory node key from the manifest. Not wired yet.
|
|
285
|
+
return { ok: false, reason: "single_key_verification_unsupported" };
|
|
286
|
+
}
|
|
287
|
+
const persistence = new FileRegistrationPersistence({ agentDir: deps.agentDir, logger: deps.logger });
|
|
288
|
+
const share = await persistence.loadActiveFrostKeyShare();
|
|
289
|
+
if (!share)
|
|
290
|
+
return { ok: false, reason: "no_frost_share" };
|
|
291
|
+
let primaryPubkey = null;
|
|
292
|
+
try {
|
|
293
|
+
const dc = decode(Buffer.from(share.commitmentsCbor));
|
|
294
|
+
if (Array.isArray(dc) && dc.length > 0) {
|
|
295
|
+
const c0 = dc[0];
|
|
296
|
+
primaryPubkey = c0 instanceof Uint8Array ? c0 : Buffer.isBuffer(c0) ? new Uint8Array(c0) : null;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
catch (err) {
|
|
300
|
+
deps.logger.warn("session.unilateral.certificate.share.decode.failed", {
|
|
301
|
+
agentPubkey: deps.agentPubkeyHex,
|
|
302
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
303
|
+
});
|
|
304
|
+
return { ok: false, reason: "share_decode_failed" };
|
|
305
|
+
}
|
|
306
|
+
if (!primaryPubkey || primaryPubkey.length !== 32)
|
|
307
|
+
return { ok: false, reason: "no_primary_pubkey" };
|
|
308
|
+
const verifier = new FrostThresholdSigner({ threshold: 1, participants: 1 }, Buffer.from(deps.agentPubkeyHex, "hex"));
|
|
309
|
+
const valid = verifier.verifySignature(cert.frostSignature, tbs, "cello-frost-seal-v1", primaryPubkey);
|
|
310
|
+
return valid ? { ok: true } : { ok: false, reason: "signature_invalid" };
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* M7 legibility-TBS-binding: verify a BILATERAL session_sealed certificate's signature over the
|
|
314
|
+
* legibility-bound TBS, channel-independently.
|
|
315
|
+
*
|
|
316
|
+
* KEY STRUCTURE (why this is asymmetric): the seal's FROST signature is produced by the
|
|
317
|
+
* INITIATOR's group key (commitments[0] of the initiator's DKG share) + the directory's K_server
|
|
318
|
+
* shares. So only the INITIATOR holds the verification key locally — it loads its OWN primary from
|
|
319
|
+
* its share and checks `signer_pubkey === own primary`, then verifies the FROST signature over
|
|
320
|
+
* `buildSealTbs ‖ legibilityHash`. A tampered legibility (answered / content_frontier_seq /
|
|
321
|
+
* attestation_mode — carried unsigned on the frame) changes the hash → the signature fails → REJECT.
|
|
322
|
+
*
|
|
323
|
+
* The NON-INITIATOR (the responder B) does NOT hold the initiator's group key, so it cannot
|
|
324
|
+
* channel-independently verify here — it ACCEPTS (`verified:false`). This is sound for the LIVE
|
|
325
|
+
* path: session_sealed arrives over the daemon↔directory libp2p Noise channel (authenticated +
|
|
326
|
+
* encrypted), so it is not MITM-tamperable in transit. The binding's primary value is OUT-OF-BAND:
|
|
327
|
+
* any holder of the initiator's primary (an arbitrator) can verify an exported cert's legibility.
|
|
328
|
+
* Giving the responder the initiator's primary via the FROST-signed session establishment (so it,
|
|
329
|
+
* too, can verify locally) is a follow-on.
|
|
330
|
+
*
|
|
331
|
+
* `legibility` MUST be the AS-RECEIVED wire object (not a normalised copy) — the directory signed
|
|
332
|
+
* over the canonical hash of exactly what it sent.
|
|
333
|
+
*/
|
|
334
|
+
export async function verifyBilateralSealCertificate(deps, cert) {
|
|
335
|
+
if (cert.signatureType !== "frost")
|
|
336
|
+
return { ok: true, verified: false };
|
|
337
|
+
if (cert.signerPubkey.length !== 32)
|
|
338
|
+
return { ok: false, reason: "no_signer_pubkey" };
|
|
339
|
+
const signerHex = Buffer.from(cert.signerPubkey).toString("hex");
|
|
340
|
+
const persistence = new FileRegistrationPersistence({ agentDir: deps.agentDir, logger: deps.logger });
|
|
341
|
+
const share = await persistence.loadActiveFrostKeyShare();
|
|
342
|
+
if (!share)
|
|
343
|
+
return { ok: true, verified: false }; // no share to verify with — accept (Noise-delivered)
|
|
344
|
+
let ownPrimary = null;
|
|
345
|
+
try {
|
|
346
|
+
const dc = decode(Buffer.from(share.commitmentsCbor));
|
|
347
|
+
if (Array.isArray(dc) && dc.length > 0) {
|
|
348
|
+
const c0 = dc[0];
|
|
349
|
+
ownPrimary = c0 instanceof Uint8Array ? c0 : Buffer.isBuffer(c0) ? new Uint8Array(c0) : null;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
return { ok: true, verified: false };
|
|
354
|
+
}
|
|
355
|
+
if (!ownPrimary || ownPrimary.length !== 32)
|
|
356
|
+
return { ok: true, verified: false };
|
|
357
|
+
// The seal is signed by the INITIATOR's primary (group) key. This party can verify against a
|
|
358
|
+
// key it holds independently: its OWN primary (when it is the initiator) or the counterparty's
|
|
359
|
+
// primary from the FROST-signed SessionAssignment (when it is the responder). SI-003: the signer
|
|
360
|
+
// must be one of these — never a key supplied only by the (untrusted) cert frame.
|
|
361
|
+
const cpHex = deps.counterpartyPrimaryHex ?? null;
|
|
362
|
+
if (signerHex === Buffer.from(ownPrimary).toString("hex")) {
|
|
363
|
+
// initiator path — verify against own primary.
|
|
364
|
+
}
|
|
365
|
+
else if (cpHex && signerHex === cpHex.toLowerCase()) {
|
|
366
|
+
// responder path — verify against the known counterparty primary.
|
|
367
|
+
}
|
|
368
|
+
else if (cpHex) {
|
|
369
|
+
// We know the counterparty primary and the signer is NEITHER participant → unknown signer.
|
|
370
|
+
return { ok: false, reason: "signer_not_a_session_participant" };
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
// We do not hold the signer's key (no counterparty primary recorded) → cannot verify; accept
|
|
374
|
+
// (the live frame arrived over the authenticated Noise channel; the binding aids out-of-band).
|
|
375
|
+
return { ok: true, verified: false };
|
|
376
|
+
}
|
|
377
|
+
const tbs = bindLegibilityToTbs(buildSealTbs(cert.sessionId, cert.sealedRoot, cert.leafCount, cert.closeTimestamp), cert.legibility);
|
|
378
|
+
const verifier = new FrostThresholdSigner({ threshold: 1, participants: 1 }, Buffer.from(deps.agentPubkeyHex, "hex"));
|
|
379
|
+
const valid = verifier.verifySignature(cert.frostSignature, tbs, "cello-frost-seal-v1", cert.signerPubkey);
|
|
380
|
+
return valid ? { ok: true, verified: true } : { ok: false, reason: "signature_invalid" };
|
|
381
|
+
}
|
|
382
|
+
export function wireSessionCeremonyHandler(deps) {
|
|
383
|
+
return deps.signaling.registerInboundHandler((frame) => {
|
|
384
|
+
if (frame["type"] !== "ceremony_request")
|
|
385
|
+
return;
|
|
386
|
+
void (async () => {
|
|
387
|
+
const ceremonyId = typeof frame["ceremony_id"] === "string" ? frame["ceremony_id"] : undefined;
|
|
388
|
+
const tbsRaw = frame["tbs"];
|
|
389
|
+
const tbs = tbsRaw instanceof Uint8Array ? tbsRaw : Buffer.isBuffer(tbsRaw) ? new Uint8Array(tbsRaw) : null;
|
|
390
|
+
const context = typeof frame["context"] === "string" ? frame["context"] : undefined;
|
|
391
|
+
// L3: the reply send is best-effort — a throw here must not become an unhandled
|
|
392
|
+
// rejection in this fire-and-forget handler.
|
|
393
|
+
const reply = async (signature) => {
|
|
394
|
+
if (!ceremonyId)
|
|
395
|
+
return;
|
|
396
|
+
try {
|
|
397
|
+
await deps.signaling.sendRaw({ type: "ceremony_result", ceremony_id: ceremonyId, signature });
|
|
398
|
+
}
|
|
399
|
+
catch (err) {
|
|
400
|
+
deps.logger.warn("session.ceremony.reply.failed", {
|
|
401
|
+
agentName: deps.agentName,
|
|
402
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
// L1: a request with no addressable ceremony_id cannot be replied to (the reply is
|
|
407
|
+
// keyed by it) — log and drop rather than send an unkeyed result.
|
|
408
|
+
if (!ceremonyId) {
|
|
409
|
+
deps.logger.warn("session.ceremony.abort", { agentName: deps.agentName, reason: "no_ceremony_id" });
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
if (!tbs || !context) {
|
|
413
|
+
deps.logger.warn("session.ceremony.abort", { agentName: deps.agentName, reason: !tbs ? "no_tbs" : "no_context" });
|
|
414
|
+
await reply(null);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
// H1 / M1 / concurrency: reconstruct a FRESH signer (fresh directory endpoint +
|
|
418
|
+
// NetworkDirectoryNode) PER ceremony. No caching — so signaling reconnecting to a
|
|
419
|
+
// different directory node is picked up, a transient reconstruction failure retries
|
|
420
|
+
// on the next request, and concurrent ceremonies never share one signer's FROST
|
|
421
|
+
// state. (storeDkgResult is idempotent for the same share; reconstruction is cheap.)
|
|
422
|
+
const signer = await reconstructThresholdSigner(deps);
|
|
423
|
+
if (!signer) {
|
|
424
|
+
deps.logger.warn("session.ceremony.abort", { agentName: deps.agentName, reason: "no_signer" });
|
|
425
|
+
await reply(null);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
try {
|
|
429
|
+
const result = await signer.participateInCeremony(ceremonyId, tbs, context);
|
|
430
|
+
deps.logger.info("session.ceremony.participated", {
|
|
431
|
+
agentName: deps.agentName,
|
|
432
|
+
ceremonyId: ceremonyId.slice(0, 16),
|
|
433
|
+
ok: result.ok,
|
|
434
|
+
});
|
|
435
|
+
await reply(result.ok ? new Uint8Array(result.signature) : null);
|
|
436
|
+
}
|
|
437
|
+
catch (err) {
|
|
438
|
+
deps.logger.warn("session.ceremony.failed", {
|
|
439
|
+
agentName: deps.agentName,
|
|
440
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
441
|
+
});
|
|
442
|
+
await reply(null);
|
|
443
|
+
}
|
|
444
|
+
})();
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
//# sourceMappingURL=session-ceremony.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-ceremony.js","sourceRoot":"","sources":["../src/session-ceremony.ts"],"names":[],"mappings":"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"}
|