@cello-protocol/protocol-types 0.0.15 → 0.0.16

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/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export type { SessionAssignment, SessionAssignmentFrost, SessionAssignmentSingle
7
7
  export type { MlDsaKeyProvider, MlDsaVerifier, PseudonymBinding, Endorsement, Attestation, ConnectionPackage, EndorsementValidationStatus, AttestationValidationStatus, ValidatedEndorsement, ValidatedAttestation, PackageValidationResult, BuildPseudonymBindingResult, SignalCondition, SignalRequirement, ConnectionPolicy, DirectoryContext, } from "./connection-package.js";
8
8
  export { MAX_PSEUDONYM_LABEL_BYTES, ML_DSA_PUBKEY_BYTES, ML_DSA_SIGNATURE_BYTES, buildPseudonymBinding, verifyPseudonymBinding, verifyEndorsement, verifyAttestation, validateConnectionPackage, encodeConnectionPackage, decodeConnectionPackage, } from "./connection-package.js";
9
9
  export type { RegisterRequest, DkgComplete, DkgReady, RegisterSuccess, RegisterError, RegisterErrorReason, AgentProfile, RegistrationState, } from "./registration.js";
10
+ export type { PrimaryTransferRequest, PrimaryTransferAck, PrimaryTransferError, } from "./primary-transfer.js";
10
11
  export { buildAgentRevocationTbs, AGENT_REVOCATION_DOMAIN } from "./revocation.js";
11
12
  export type { RevokeAgentRequest, AgentRevocationAck, AgentRevocationError, AgentRevocationErrorReason, } from "./revocation.js";
12
13
  export type { ConnectionRequest, ConnectionRequestInbound, ConnectionResponse, ConnectionResponseVerdict, ConnectionEstablished, ConnectionRejected, ConnectionInsufficient, ConnectionRequestError, ConnectionRequestErrorReason, DisclosureRequest, DisclosureRequestItem, DisclosureRequestInbound, DisclosureResponse, DisclosureResponseInbound, ConnectionRecord, PendingConnectionRequest, ClientConnectionRecord, SessionRequestM3, SessionRequestM3ErrorReason, } from "./connection-request.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AAEvB,YAAY,EAAE,kBAAkB,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,gBAAgB,EAChB,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,4BAA4B,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACjK,YAAY,EACV,iBAAiB,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,eAAe,EAAE,iBAAiB,EAAE,WAAW,EACnH,gBAAgB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,aAAa,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,YAAY,EAChI,eAAe,EAAE,cAAc,EAAE,yBAAyB,EAAE,0BAA0B,GACvF,MAAM,cAAc,CAAC;AAEtB,YAAY,EACV,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,2BAA2B,EAC3B,2BAA2B,EAC3B,oBAAoB,EACpB,oBAAoB,EACpB,uBAAuB,EACvB,2BAA2B,EAC3B,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,yBAAyB,EACzB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,EACjB,iBAAiB,EACjB,yBAAyB,EACzB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,yBAAyB,CAAC;AAGjC,YAAY,EACV,eAAe,EACf,WAAW,EACX,QAAQ,EACR,eAAe,EACf,aAAa,EACb,mBAAmB,EACnB,YAAY,EACZ,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EACV,sBAAsB,EACtB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AACnF,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,0BAA0B,GAC3B,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EACV,iBAAiB,EACjB,wBAAwB,EACxB,kBAAkB,EAClB,yBAAyB,EACzB,qBAAqB,EACrB,kBAAkB,EAClB,sBAAsB,EACtB,sBAAsB,EACtB,4BAA4B,EAC5B,iBAAiB,EACjB,qBAAqB,EACrB,wBAAwB,EACxB,kBAAkB,EAClB,yBAAyB,EACzB,gBAAgB,EAChB,wBAAwB,EACxB,sBAAsB,EACtB,gBAAgB,EAChB,2BAA2B,GAC5B,MAAM,yBAAyB,CAAC;AAEjC,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,eAAe,EACf,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AAExB,YAAY,EACV,wBAAwB,EACxB,yBAAyB,EACzB,0BAA0B,EAC1B,yBAAyB,EACzB,0BAA0B,EAC1B,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAG5B,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,aAAa,GACd,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,EACzB,gBAAgB,GACjB,MAAM,eAAe,CAAC;AAGvB,YAAY,EACV,mBAAmB,EACnB,sBAAsB,EACtB,kBAAkB,EAClB,wBAAwB,EACxB,uBAAuB,EACvB,+BAA+B,GAChC,MAAM,uBAAuB,CAAC;AAG/B,YAAY,EACV,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,4BAA4B,EAC5B,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EACvB,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EACV,eAAe,EACf,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,6BAA6B,EAC7B,6BAA6B,GAC9B,MAAM,uBAAuB,CAAC"}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,gBAAgB,EAChB,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,4BAA4B,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AA0BjK,OAAO,EACL,yBAAyB,EACzB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,EACjB,iBAAiB,EACjB,yBAAyB,EACzB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,yBAAyB,CAAC;AAcjC,OAAO,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AA6DnF,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,EACzB,gBAAgB,GACjB,MAAM,eAAe,CAAC;AAuBvB,OAAO,EACL,4BAA4B,EAC5B,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EACvB,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAO/B,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,6BAA6B,EAC7B,6BAA6B,GAC9B,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,gBAAgB,EAChB,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,4BAA4B,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AA0BjK,OAAO,EACL,yBAAyB,EACzB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,EACjB,iBAAiB,EACjB,yBAAyB,EACzB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,yBAAyB,CAAC;AAoBjC,OAAO,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AA6DnF,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,EACzB,gBAAgB,GACjB,MAAM,eAAe,CAAC;AAuBvB,OAAO,EACL,4BAA4B,EAC5B,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EACvB,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAO/B,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,6BAA6B,EAC7B,6BAA6B,GAC9B,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * CELLO Primary/Standby Transfer Wire Types — M8C-PRIMARY-1
3
+ *
4
+ * Design: docs/planning/user-stories/m8c/M8C-PRIMARY-DESIGN.md (Decision 4, revised across 3
5
+ * passes: adversarial security review, a code-level correction to the real client-coordinated-
6
+ * per-node pattern, and a release-attestation crypto fix reusing the existing FROST ceremony).
7
+ * Mirrors registration.ts's DKG protocol shape exactly — CELLO has no cross-node RPC/consensus
8
+ * anywhere; "exactly one Primary" is enforced by the SAME pattern DKG already uses: the daemon
9
+ * (client) dials each of T-of-N directory nodes holding the agent's FROST shares directly, and
10
+ * each node independently verifies + records the claim locally. There is no directory-to-directory
11
+ * frame here — only client-to-directory, repeated per node.
12
+ *
13
+ * Phase P — Pseudocode
14
+ * ─────────────────────────────────────────────────────────────────────────────
15
+ * Primary-transfer flow (on the existing authenticated /cello/signaling/1.0.0 stream, once per
16
+ * directory node the daemon dials):
17
+ *
18
+ * CLIENT (new Primary) → DIRECTORY (one node):
19
+ * 1. Client sends primary_transfer_request { k_local_pubkey, new_daemon_id, old_daemon_id,
20
+ * nonce, timestamp, release_signature }. release_signature is a REAL FROST threshold
21
+ * signature (core/crypto's participateInCeremony, context CONTEXT_PRIMARY_RELEASE — see
22
+ * core/crypto/src/frost/types.ts) produced by the OLD Primary over canonical-bytes
23
+ * (k_local_pubkey, new_daemon_id, old_daemon_id, nonce, timestamp). This is NOT a K_local
24
+ * signature (both daemons share K_local after pairing, per Decision 1 — a K_local signature
25
+ * cannot distinguish which physical device produced it). A FROST signature under this
26
+ * context can ONLY be produced by a device that currently holds the agent's FROST share —
27
+ * exactly the property that distinguishes a genuine old Primary from a Standby that never
28
+ * received the share (Decision 3).
29
+ *
30
+ * DIRECTORY validates (per node, independently):
31
+ * a. old_daemon_id matches THIS node's own currently-recorded primary_holder.holding_daemon_id
32
+ * → else primary_transfer_error { reason: "not_registered" } (a stale/wrong claim about who
33
+ * is releasing is rejected before any cryptographic check)
34
+ * b. release_signature verifies via verifySignature(release_signature, tbs, CONTEXT_PRIMARY_RELEASE,
35
+ * primary_pubkey) against THIS node's own recorded primary_pubkey for k_local_pubkey → else
36
+ * primary_transfer_error { reason: "release_not_verified" }
37
+ * c. nonce not already consumed at this node (single-use, mirrors the pre-auth capability's
38
+ * consumed_at pattern) → else primary_transfer_error { reason: "nonce_reused" }
39
+ * d. timestamp within a bounded freshness window (replay defense) → else
40
+ * primary_transfer_error { reason: "stale_request" }
41
+ *
42
+ * DIRECTORY on success (this node only):
43
+ * - Upserts its own local primary_holder row: holding_daemon_id = new_daemon_id,
44
+ * last_attested_at = now()
45
+ * - Sends primary_transfer_ack { node_id } — the daemon tallies acks per Decision 4; T-of-N acks
46
+ * is what "transfer complete" means, never a cross-node signal
47
+ *
48
+ * CLIENT tallies primary_transfer_ack across its T-of-N dial attempts (mirrors exactly how it
49
+ * already tallies DKG-round successes when driving DKG per-node) and does not proceed to resume as
50
+ * Primary (attempt a FROST ceremony, accept new sessions) until T acks are collected.
51
+ *
52
+ * Ceremony-gate check (separate, existing FROST signing-request path, extended): a directory node
53
+ * refuses to let a ceremony proceed through its own participation unless the requesting
54
+ * connection's daemon_id matches that node's own primary_holder.holding_daemon_id.
55
+ *
56
+ * Security invariants:
57
+ * SI-001: release_signature MUST be a real FROST threshold signature under CONTEXT_PRIMARY_RELEASE
58
+ * (verified against the node's own recorded primary_pubkey) — NEVER a K_local signature or any
59
+ * signature a Standby (K_local-holder, non-share-holder) could produce.
60
+ * SI-002: nonce is single-use per node (replay defense, mirrors the existing pre-auth capability
61
+ * `consumed_at` pattern already proven in production).
62
+ * SI-003: this frame set carries NO secret material — no FROST share, no K_local seed. The share
63
+ * itself is never transmitted to the directory (M8C-PRIMARY-DESIGN Decision 3); the directory
64
+ * only ever sees a threshold signature it can verify, never anything it could use to forge one.
65
+ * SI-004: old_daemon_id is checked against this node's OWN recorded holder before any
66
+ * cryptographic verification — an attacker cannot substitute a fabricated old_daemon_id and
67
+ * hope the signature check alone catches it (defense in depth, cheap check first).
68
+ * ─────────────────────────────────────────────────────────────────────────────
69
+ */
70
+ /**
71
+ * Step 1: the new Primary attests a transfer to one directory node. Sent on that node's
72
+ * authenticated signaling stream. The daemon repeats this per node it dials.
73
+ */
74
+ export interface PrimaryTransferRequest {
75
+ type: "primary_transfer_request";
76
+ /** Hex-encoded K_local public key (32 bytes) — the shared identity both daemons hold. */
77
+ k_local_pubkey: string;
78
+ /** Fresh per-device UUID identifying the daemon CLAIMING Primary status. */
79
+ new_daemon_id: string;
80
+ /** The per-device UUID of the daemon RELINQUISHING Primary status. */
81
+ old_daemon_id: string;
82
+ /**
83
+ * Hex-encoded 64-byte FROST threshold signature (core/crypto's participateInCeremony, context
84
+ * CONTEXT_PRIMARY_RELEASE) over canonical-bytes(k_local_pubkey, new_daemon_id, old_daemon_id,
85
+ * nonce, timestamp), produced by the OLD Primary. Verified via verifySignature(...,
86
+ * CONTEXT_PRIMARY_RELEASE, primary_pubkey) — proves genuine possession of the FROST share at
87
+ * release time (SI-001). NOT a K_local signature — see the module doc above for why.
88
+ */
89
+ release_signature: string;
90
+ /** Single-use nonce (SI-002) — mirrors the existing pre-auth capability pattern. */
91
+ nonce: string;
92
+ /** Unix ms timestamp — bounds replay window (freshness check, not itself the security boundary). */
93
+ timestamp: number;
94
+ }
95
+ /**
96
+ * This node has recorded new_daemon_id as the current Primary holder for k_local_pubkey. This is
97
+ * a PER-NODE acknowledgment, never a cross-node commit signal — the client tallies these across
98
+ * its T-of-N dial attempts (M8C-PRIMARY-DESIGN Decision 4).
99
+ */
100
+ export interface PrimaryTransferAck {
101
+ type: "primary_transfer_ack";
102
+ /** The stable manifest label of the node that accepted (e.g. "us-east-1"). */
103
+ node_id: string;
104
+ }
105
+ /**
106
+ * This node rejected the transfer request. SI-001/SI-002/SI-004/freshness failures all surface
107
+ * here with a distinct, named reason — never a generic failure.
108
+ */
109
+ export interface PrimaryTransferError {
110
+ type: "primary_transfer_error";
111
+ reason: "release_not_verified" | "nonce_reused" | "stale_request" | "not_registered";
112
+ }
113
+ //# sourceMappingURL=primary-transfer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"primary-transfer.d.ts","sourceRoot":"","sources":["../src/primary-transfer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AAIH;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,0BAA0B,CAAC;IACjC,yFAAyF;IACzF,cAAc,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,aAAa,EAAE,MAAM,CAAC;IACtB,sEAAsE;IACtE,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oFAAoF;IACpF,KAAK,EAAE,MAAM,CAAC;IACd,oGAAoG;IACpG,SAAS,EAAE,MAAM,CAAC;CACnB;AAID;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,sBAAsB,CAAC;IAC7B,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,wBAAwB,CAAC;IAC/B,MAAM,EAAE,sBAAsB,GAAG,cAAc,GAAG,eAAe,GAAG,gBAAgB,CAAC;CACtF"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * CELLO Primary/Standby Transfer Wire Types — M8C-PRIMARY-1
3
+ *
4
+ * Design: docs/planning/user-stories/m8c/M8C-PRIMARY-DESIGN.md (Decision 4, revised across 3
5
+ * passes: adversarial security review, a code-level correction to the real client-coordinated-
6
+ * per-node pattern, and a release-attestation crypto fix reusing the existing FROST ceremony).
7
+ * Mirrors registration.ts's DKG protocol shape exactly — CELLO has no cross-node RPC/consensus
8
+ * anywhere; "exactly one Primary" is enforced by the SAME pattern DKG already uses: the daemon
9
+ * (client) dials each of T-of-N directory nodes holding the agent's FROST shares directly, and
10
+ * each node independently verifies + records the claim locally. There is no directory-to-directory
11
+ * frame here — only client-to-directory, repeated per node.
12
+ *
13
+ * Phase P — Pseudocode
14
+ * ─────────────────────────────────────────────────────────────────────────────
15
+ * Primary-transfer flow (on the existing authenticated /cello/signaling/1.0.0 stream, once per
16
+ * directory node the daemon dials):
17
+ *
18
+ * CLIENT (new Primary) → DIRECTORY (one node):
19
+ * 1. Client sends primary_transfer_request { k_local_pubkey, new_daemon_id, old_daemon_id,
20
+ * nonce, timestamp, release_signature }. release_signature is a REAL FROST threshold
21
+ * signature (core/crypto's participateInCeremony, context CONTEXT_PRIMARY_RELEASE — see
22
+ * core/crypto/src/frost/types.ts) produced by the OLD Primary over canonical-bytes
23
+ * (k_local_pubkey, new_daemon_id, old_daemon_id, nonce, timestamp). This is NOT a K_local
24
+ * signature (both daemons share K_local after pairing, per Decision 1 — a K_local signature
25
+ * cannot distinguish which physical device produced it). A FROST signature under this
26
+ * context can ONLY be produced by a device that currently holds the agent's FROST share —
27
+ * exactly the property that distinguishes a genuine old Primary from a Standby that never
28
+ * received the share (Decision 3).
29
+ *
30
+ * DIRECTORY validates (per node, independently):
31
+ * a. old_daemon_id matches THIS node's own currently-recorded primary_holder.holding_daemon_id
32
+ * → else primary_transfer_error { reason: "not_registered" } (a stale/wrong claim about who
33
+ * is releasing is rejected before any cryptographic check)
34
+ * b. release_signature verifies via verifySignature(release_signature, tbs, CONTEXT_PRIMARY_RELEASE,
35
+ * primary_pubkey) against THIS node's own recorded primary_pubkey for k_local_pubkey → else
36
+ * primary_transfer_error { reason: "release_not_verified" }
37
+ * c. nonce not already consumed at this node (single-use, mirrors the pre-auth capability's
38
+ * consumed_at pattern) → else primary_transfer_error { reason: "nonce_reused" }
39
+ * d. timestamp within a bounded freshness window (replay defense) → else
40
+ * primary_transfer_error { reason: "stale_request" }
41
+ *
42
+ * DIRECTORY on success (this node only):
43
+ * - Upserts its own local primary_holder row: holding_daemon_id = new_daemon_id,
44
+ * last_attested_at = now()
45
+ * - Sends primary_transfer_ack { node_id } — the daemon tallies acks per Decision 4; T-of-N acks
46
+ * is what "transfer complete" means, never a cross-node signal
47
+ *
48
+ * CLIENT tallies primary_transfer_ack across its T-of-N dial attempts (mirrors exactly how it
49
+ * already tallies DKG-round successes when driving DKG per-node) and does not proceed to resume as
50
+ * Primary (attempt a FROST ceremony, accept new sessions) until T acks are collected.
51
+ *
52
+ * Ceremony-gate check (separate, existing FROST signing-request path, extended): a directory node
53
+ * refuses to let a ceremony proceed through its own participation unless the requesting
54
+ * connection's daemon_id matches that node's own primary_holder.holding_daemon_id.
55
+ *
56
+ * Security invariants:
57
+ * SI-001: release_signature MUST be a real FROST threshold signature under CONTEXT_PRIMARY_RELEASE
58
+ * (verified against the node's own recorded primary_pubkey) — NEVER a K_local signature or any
59
+ * signature a Standby (K_local-holder, non-share-holder) could produce.
60
+ * SI-002: nonce is single-use per node (replay defense, mirrors the existing pre-auth capability
61
+ * `consumed_at` pattern already proven in production).
62
+ * SI-003: this frame set carries NO secret material — no FROST share, no K_local seed. The share
63
+ * itself is never transmitted to the directory (M8C-PRIMARY-DESIGN Decision 3); the directory
64
+ * only ever sees a threshold signature it can verify, never anything it could use to forge one.
65
+ * SI-004: old_daemon_id is checked against this node's OWN recorded holder before any
66
+ * cryptographic verification — an attacker cannot substitute a fabricated old_daemon_id and
67
+ * hope the signature check alone catches it (defense in depth, cheap check first).
68
+ * ─────────────────────────────────────────────────────────────────────────────
69
+ */
70
+ export {};
71
+ //# sourceMappingURL=primary-transfer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"primary-transfer.js","sourceRoot":"","sources":["../src/primary-transfer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cello-protocol/protocol-types",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
@@ -23,7 +23,7 @@
23
23
  ],
24
24
  "dependencies": {
25
25
  "cbor-x": "^1.6.4",
26
- "@cello-protocol/crypto": "0.0.17"
26
+ "@cello-protocol/crypto": "0.0.18"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@claude-flow/testing": "3.0.0-alpha.6",