@cello-protocol/daemon 0.0.194 → 0.0.195

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist/assignment-verify.d.ts +39 -11
  2. package/dist/assignment-verify.d.ts.map +1 -1
  3. package/dist/assignment-verify.js +133 -17
  4. package/dist/assignment-verify.js.map +1 -1
  5. package/dist/authorship-verification.d.ts +70 -0
  6. package/dist/authorship-verification.d.ts.map +1 -0
  7. package/dist/authorship-verification.js +441 -0
  8. package/dist/authorship-verification.js.map +1 -0
  9. package/dist/db-identity-store.d.ts +1 -0
  10. package/dist/db-identity-store.d.ts.map +1 -1
  11. package/dist/db-identity-store.js +19 -1
  12. package/dist/db-identity-store.js.map +1 -1
  13. package/dist/held-content.d.ts +141 -0
  14. package/dist/held-content.d.ts.map +1 -0
  15. package/dist/held-content.js +388 -0
  16. package/dist/held-content.js.map +1 -0
  17. package/dist/inbound-refusals.d.ts +283 -0
  18. package/dist/inbound-refusals.d.ts.map +1 -0
  19. package/dist/inbound-refusals.js +919 -0
  20. package/dist/inbound-refusals.js.map +1 -0
  21. package/dist/inbound-sessions.d.ts.map +1 -1
  22. package/dist/inbound-sessions.js +65 -27
  23. package/dist/inbound-sessions.js.map +1 -1
  24. package/dist/initiate-session-handler.d.ts.map +1 -1
  25. package/dist/initiate-session-handler.js +63 -0
  26. package/dist/initiate-session-handler.js.map +1 -1
  27. package/dist/outbound-sessions.d.ts.map +1 -1
  28. package/dist/outbound-sessions.js +108 -8
  29. package/dist/outbound-sessions.js.map +1 -1
  30. package/dist/park-recovery.d.ts +205 -0
  31. package/dist/park-recovery.d.ts.map +1 -0
  32. package/dist/park-recovery.js +600 -0
  33. package/dist/park-recovery.js.map +1 -0
  34. package/dist/refusal-notices.d.ts +196 -0
  35. package/dist/refusal-notices.d.ts.map +1 -0
  36. package/dist/refusal-notices.js +496 -0
  37. package/dist/refusal-notices.js.map +1 -0
  38. package/dist/refusal-reasons.d.ts +11 -0
  39. package/dist/refusal-reasons.d.ts.map +1 -1
  40. package/dist/refusal-reasons.js +18 -0
  41. package/dist/refusal-reasons.js.map +1 -1
  42. package/dist/registration-manager.d.ts.map +1 -1
  43. package/dist/registration-manager.js +126 -5
  44. package/dist/registration-manager.js.map +1 -1
  45. package/dist/registration-persistence.d.ts +18 -0
  46. package/dist/registration-persistence.d.ts.map +1 -1
  47. package/dist/registration-persistence.js +6 -0
  48. package/dist/registration-persistence.js.map +1 -1
  49. package/dist/session-assignment-parser.d.ts.map +1 -1
  50. package/dist/session-assignment-parser.js +23 -1
  51. package/dist/session-assignment-parser.js.map +1 -1
  52. package/dist/session-ceremony.d.ts +12 -7
  53. package/dist/session-ceremony.d.ts.map +1 -1
  54. package/dist/session-ceremony.js +12 -7
  55. package/dist/session-ceremony.js.map +1 -1
  56. package/dist/session-ephemerals.d.ts +271 -0
  57. package/dist/session-ephemerals.d.ts.map +1 -0
  58. package/dist/session-ephemerals.js +546 -0
  59. package/dist/session-ephemerals.js.map +1 -0
  60. package/dist/session-leaf-records.d.ts +132 -0
  61. package/dist/session-leaf-records.d.ts.map +1 -0
  62. package/dist/session-leaf-records.js +320 -0
  63. package/dist/session-leaf-records.js.map +1 -0
  64. package/dist/session-liveness.d.ts +135 -0
  65. package/dist/session-liveness.d.ts.map +1 -0
  66. package/dist/session-liveness.js +347 -0
  67. package/dist/session-liveness.js.map +1 -0
  68. package/dist/session-node-manager.d.ts +300 -1670
  69. package/dist/session-node-manager.d.ts.map +1 -1
  70. package/dist/session-node-manager.js +871 -9951
  71. package/dist/session-node-manager.js.map +1 -1
  72. package/dist/session-node-types.d.ts +933 -0
  73. package/dist/session-node-types.d.ts.map +1 -0
  74. package/dist/session-node-types.js +576 -0
  75. package/dist/session-node-types.js.map +1 -0
  76. package/dist/session-queries.d.ts +476 -0
  77. package/dist/session-queries.d.ts.map +1 -0
  78. package/dist/session-queries.js +1006 -0
  79. package/dist/session-queries.js.map +1 -0
  80. package/dist/session-records.d.ts +320 -0
  81. package/dist/session-records.d.ts.map +1 -0
  82. package/dist/session-records.js +792 -0
  83. package/dist/session-records.js.map +1 -0
  84. package/dist/session-salts.d.ts +390 -0
  85. package/dist/session-salts.d.ts.map +1 -0
  86. package/dist/session-salts.js +1457 -0
  87. package/dist/session-salts.js.map +1 -0
  88. package/dist/session-schema.d.ts +30 -0
  89. package/dist/session-schema.d.ts.map +1 -0
  90. package/dist/session-schema.js +786 -0
  91. package/dist/session-schema.js.map +1 -0
  92. package/dist/standing-receivers.d.ts +280 -0
  93. package/dist/standing-receivers.d.ts.map +1 -0
  94. package/dist/standing-receivers.js +1100 -0
  95. package/dist/standing-receivers.js.map +1 -0
  96. package/dist/transport-selector.d.ts +13 -0
  97. package/dist/transport-selector.d.ts.map +1 -1
  98. package/dist/transport-selector.js.map +1 -1
  99. package/dist/witness-alerts.d.ts +40 -0
  100. package/dist/witness-alerts.d.ts.map +1 -0
  101. package/dist/witness-alerts.js +102 -0
  102. package/dist/witness-alerts.js.map +1 -0
  103. package/package.json +5 -5
@@ -11,6 +11,13 @@ import type { Logger } from "./types.js";
11
11
  */
12
12
  export declare function verifyAssignmentSignature(assignment: SessionAssignment, persistence: DbRegistrationPersistence, logger: Logger, agentName: string, correlationId: string): Promise<{
13
13
  ok: true;
14
+ /**
15
+ * 038-KEYBIND. The COUNTERPARTY's FROST group public key, hex, having been proved theirs by a
16
+ * signature under their own K_local. Returned rather than left on the frame so the caller
17
+ * cannot pin a value that was never checked — the only way to hold this string is to have
18
+ * come through the binding check below.
19
+ */
20
+ counterpartyPrimaryHex: string;
14
21
  } | {
15
22
  ok: false;
16
23
  reason: string;
@@ -38,22 +45,43 @@ export declare function verifyAssignmentSignature(assignment: SessionAssignment,
38
45
  * that is a real check: a compromised directory can produce a signature, but not one that verifies
39
46
  * under a key it does not hold.
40
47
  *
41
- * ─── Two modes, and the weaker one is honest about being weaker ────────────────────────────────
48
+ * ─── 038-KEYBIND CLOSED THE CIRCLE, AND THIS PARAGRAPH REPLACES THE ONE THAT ADMITTED IT ───────
42
49
  *
43
- * PINNED (`expectedSignerHex` given): the signature must verify under the PINNED key. Non-circular.
44
- * This is the mode that matters, and it covers every repeat counterparty.
50
+ * ⚠️ THE OLD TEXT HERE IS KEPT AS EVIDENCE RATHER THAN DELETED, because it is the record of a real
51
+ * bound that someone reasoned their way to and wrote down. It said: *"UNPINNED (first contact):
52
+ * there is nothing independent to verify against, so this checks INTERNAL CONSISTENCY only… That
53
+ * does NOT authenticate the directory, and it is not claimed to."* That was true of the code as it
54
+ * stood, and the `verifyAgainst` line below really did fall back to `assignment.signer_pubkey` — a
55
+ * field of the very document under verification. The signature therefore always verified, and the
56
+ * key it verified under was then PINNED as the counterparty's identity forever.
45
57
  *
46
- * UNPINNED (first contact): there is nothing independent to verify against, so this checks INTERNAL
47
- * CONSISTENCY only the signature verifies over the TBS recomputed from the assignment's own
48
- * contents, under the key the frame names. That does NOT authenticate the directory, and it is not
49
- * claimed to. What it does catch is a TAMPERED or garbage assignment one whose fields were altered
50
- * after signing, or whose signature is noise — which today reaches the seal path unchallenged and
51
- * gets PINNED, poisoning every later session with that counterparty. Refusing it here is what stops
52
- * a bad first contact becoming a permanent one.
58
+ * It is no longer true, and the thing that changed is not a stronger check on the same evidence
59
+ * it is NEW evidence on the wire. `participant_a_key_binding` is a signature by the INITIATOR's own
60
+ * K_local naming their group key. No directory holds that key, so no directory can name a group key
61
+ * of its choosing. The group key stops being something the frame ASSERTS and becomes something a
62
+ * signature PROVES.
63
+ *
64
+ * ─── The order of the two checks is the fix, not an optimisation ───────────────────────────────
65
+ *
66
+ * The binding is verified FIRST, and the threshold signature is then verified under the key the
67
+ * binding proved. There is no path to the signature check that does not pass through it: `signer`
68
+ * is not used as a verification key anywhere below, and a missing or bad binding returns before the
69
+ * TBS is even built.
70
+ *
71
+ * ─── Two modes, and neither is circular any more ───────────────────────────────────────────────
72
+ *
73
+ * PINNED (`expectedSignerHex` given): the signature must verify under the PINNED key — a value THIS
74
+ * daemon recorded in an earlier session, which no directory can retroactively change. The binding
75
+ * still runs; the pin is now a consistency check ACROSS TIME rather than the sole authority.
76
+ *
77
+ * FIRST CONTACT: the signature verifies under the group key `participant_a`'s identity key vouched
78
+ * for. What it still cannot tell you is whether `participant_a` is who you think they are — that is
79
+ * the operator's out-of-band problem and always was — but a directory can no longer substitute a
80
+ * group key, which is what this function existed to be unable to say.
53
81
  */
54
82
  export declare function verifyInboundAssignment(assignment: SessionAssignment, expectedSignerHex: string | null): {
55
83
  ok: true;
56
- mode: "pinned" | "internal";
84
+ mode: "pinned" | "bound";
57
85
  } | {
58
86
  ok: false;
59
87
  reason: string;
@@ -1 +1 @@
1
- {"version":3,"file":"assignment-verify.d.ts","sourceRoot":"","sources":["../src/assignment-verify.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,iBAAiB,EAC7B,WAAW,EAAE,yBAAyB,EACtC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA4GzE;AAGD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,iBAAiB,EAC7B,iBAAiB,EAAE,MAAM,GAAG,IAAI,GAC/B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAwD3F"}
1
+ {"version":3,"file":"assignment-verify.d.ts","sourceRoot":"","sources":["../src/assignment-verify.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,iBAAiB,EAC7B,WAAW,EAAE,yBAAyB,EACtC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GACpB,OAAO,CACN;IACE,EAAE,EAAE,IAAI,CAAC;IACT;;;;;OAKG;IACH,sBAAsB,EAAE,MAAM,CAAC;CAChC,GACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAClD,CAkKA;AAGD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,iBAAiB,EAC7B,iBAAiB,EAAE,MAAM,GAAG,IAAI,GAC/B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAsGxF"}
@@ -14,7 +14,7 @@
14
14
  // removed with the downgrade refusal (review F1). Verifying `directory_signature` against the
15
15
  // `directory_pubkey` riding beside it in the same unsigned frame checks a key against itself, and
16
16
  // having that call available is how a future edit reintroduces the bypass.
17
- import { verifyFrostSignature, CONTEXT_SESSION_ESTABLISHMENT } from "@cello-protocol/crypto";
17
+ import { verifyFrostSignature, verifyKeyBinding, CONTEXT_SESSION_ESTABLISHMENT } from "@cello-protocol/crypto";
18
18
  import { buildSessionEstablishmentTbs, computeGenesisPrevRoot } from "@cello-protocol/protocol-types";
19
19
  /**
20
20
  * DOD-M15-ASSIGN-1 — verify a session assignment's signature before anything trusts it.
@@ -109,7 +109,59 @@ export async function verifyAssignmentSignature(assignment, persistence, logger,
109
109
  guidance: "The directory's session assignment did not verify against the key that should have signed it. Nothing was established. Retry the session; if it repeats, the directory you reached is not producing valid assignments and cello status will show which one that is.",
110
110
  };
111
111
  }
112
- return { ok: true };
112
+ /**
113
+ * 038-KEYBIND — LEARN THE COUNTERPARTY'S GROUP KEY, AND ONLY IF THEY VOUCHED FOR IT.
114
+ *
115
+ * The initiator never used to learn the responder's group key at all. The cost was concrete and
116
+ * one-directional: when the RESPONDER closed the conversation first, the seal certificate came
117
+ * back signed by their group key, this daemon held no copy, and it accepted the certificate
118
+ * with `verified:false` / `signer_key_not_held`. The receipt for a conversation you had was
119
+ * taken on faith in the channel it arrived on.
120
+ *
121
+ * `participant_b_primary_pubkey` alone would not fix that — it would move the same circularity
122
+ * to the other side, where a directory names a group key and we write it down. The BINDING is
123
+ * what makes it a fact: a signature under `participant_b`'s K_local. No directory holds that
124
+ * key, so no directory can put a group key of its choosing here.
125
+ *
126
+ * ⚠️ THE ANCHOR IS `participant_b.pubkey`, AND THIS FUNCTION DOES NOT CHECK IT. An earlier draft
127
+ * of this note said the caller "has already compared" it against the operator's `target_pubkey`;
128
+ * it has not — `outbound-sessions.ts` runs that comparison ~50 lines AFTER this call, and
129
+ * getting the order backwards in a comment is how someone later moves the pinning above it.
130
+ *
131
+ * What makes the returned key safe to pin is that the caller RETURNS on a target mismatch, so
132
+ * nothing reaches `recordCounterpartyPrimary` unless `participant_b` is the counterparty the
133
+ * operator asked for. The ordering is load-bearing and lives at the call site, not here.
134
+ */
135
+ const counterpartyBinding = assignment.participant_b_key_binding;
136
+ const counterpartyPrimary = assignment.participant_b_primary_pubkey;
137
+ if (!counterpartyBinding || !counterpartyPrimary) {
138
+ // ABSENT AND MALFORMED TAKE THIS PATH TOGETHER (the parser maps a wrong-length value to
139
+ // undefined). An attacker who cannot forge a binding evades a mismatch check by supplying
140
+ // none, so "we could not tell" must cost exactly what "it was wrong" costs.
141
+ logger.error("session.assignment.counterparty_binding_absent", {
142
+ agentName, correlationId,
143
+ hasBinding: !!counterpartyBinding, hasPrimary: !!counterpartyPrimary,
144
+ impact: "the assignment carried no proof that the counterparty's threshold group key is theirs, so this agent could not learn a key it would later have to trust for their seal; the session was refused before any dial and nothing was sent",
145
+ });
146
+ return {
147
+ ok: false,
148
+ reason: "assignment_counterparty_binding_absent",
149
+ guidance: "The directory returned a session assignment without the counterparty's key binding — the signature that proves their threshold key belongs to the identity you asked for. Nothing was opened and nothing you wrote was sent. Retry cello_initiate_session; a different directory node will serve it. If it repeats on every node, the counterparty registered against a directory that predates this proof and they need to re-register before you can reach them.",
150
+ };
151
+ }
152
+ if (!verifyKeyBinding(counterpartyBinding, assignment.participant_b.pubkey, counterpartyPrimary)) {
153
+ logger.error("session.assignment.counterparty_binding_invalid", {
154
+ agentName, correlationId,
155
+ counterpartyPrefix: Buffer.from(assignment.participant_b.pubkey).toString("hex").slice(0, 16),
156
+ impact: "the assignment named a threshold group key for the counterparty that their own identity key has not vouched for; it was refused before any dial, so no conversation was opened and nothing was sent",
157
+ });
158
+ return {
159
+ ok: false,
160
+ reason: "assignment_counterparty_binding_invalid",
161
+ guidance: "The directory named a threshold key for your counterparty that their own identity key has not signed for. This is refused rather than reported because the observation cannot tell a directory fault from an attempt to put a key it controls in their place; cause undetermined. Run cello_status to see which node answered and retry — if it repeats across nodes, confirm the counterparty's pubkey with their operator out of band before trying again.",
162
+ };
163
+ }
164
+ return { ok: true, counterpartyPrimaryHex: Buffer.from(counterpartyPrimary).toString("hex") };
113
165
  }
114
166
  }
115
167
  /**
@@ -134,18 +186,39 @@ export async function verifyAssignmentSignature(assignment, persistence, logger,
134
186
  * that is a real check: a compromised directory can produce a signature, but not one that verifies
135
187
  * under a key it does not hold.
136
188
  *
137
- * ─── Two modes, and the weaker one is honest about being weaker ────────────────────────────────
189
+ * ─── 038-KEYBIND CLOSED THE CIRCLE, AND THIS PARAGRAPH REPLACES THE ONE THAT ADMITTED IT ───────
190
+ *
191
+ * ⚠️ THE OLD TEXT HERE IS KEPT AS EVIDENCE RATHER THAN DELETED, because it is the record of a real
192
+ * bound that someone reasoned their way to and wrote down. It said: *"UNPINNED (first contact):
193
+ * there is nothing independent to verify against, so this checks INTERNAL CONSISTENCY only… That
194
+ * does NOT authenticate the directory, and it is not claimed to."* That was true of the code as it
195
+ * stood, and the `verifyAgainst` line below really did fall back to `assignment.signer_pubkey` — a
196
+ * field of the very document under verification. The signature therefore always verified, and the
197
+ * key it verified under was then PINNED as the counterparty's identity forever.
198
+ *
199
+ * It is no longer true, and the thing that changed is not a stronger check on the same evidence —
200
+ * it is NEW evidence on the wire. `participant_a_key_binding` is a signature by the INITIATOR's own
201
+ * K_local naming their group key. No directory holds that key, so no directory can name a group key
202
+ * of its choosing. The group key stops being something the frame ASSERTS and becomes something a
203
+ * signature PROVES.
204
+ *
205
+ * ─── The order of the two checks is the fix, not an optimisation ───────────────────────────────
138
206
  *
139
- * PINNED (`expectedSignerHex` given): the signature must verify under the PINNED key. Non-circular.
140
- * This is the mode that matters, and it covers every repeat counterparty.
207
+ * The binding is verified FIRST, and the threshold signature is then verified under the key the
208
+ * binding proved. There is no path to the signature check that does not pass through it: `signer`
209
+ * is not used as a verification key anywhere below, and a missing or bad binding returns before the
210
+ * TBS is even built.
141
211
  *
142
- * UNPINNED (first contact): there is nothing independent to verify against, so this checks INTERNAL
143
- * CONSISTENCY only — the signature verifies over the TBS recomputed from the assignment's own
144
- * contents, under the key the frame names. That does NOT authenticate the directory, and it is not
145
- * claimed to. What it does catch is a TAMPERED or garbage assignment one whose fields were altered
146
- * after signing, or whose signature is noise which today reaches the seal path unchallenged and
147
- * gets PINNED, poisoning every later session with that counterparty. Refusing it here is what stops
148
- * a bad first contact becoming a permanent one.
212
+ * ─── Two modes, and neither is circular any more ───────────────────────────────────────────────
213
+ *
214
+ * PINNED (`expectedSignerHex` given): the signature must verify under the PINNED key a value THIS
215
+ * daemon recorded in an earlier session, which no directory can retroactively change. The binding
216
+ * still runs; the pin is now a consistency check ACROSS TIME rather than the sole authority.
217
+ *
218
+ * FIRST CONTACT: the signature verifies under the group key `participant_a`'s identity key vouched
219
+ * for. What it still cannot tell you is whether `participant_a` is who you think they are — that is
220
+ * the operator's out-of-band problem and always was — but a directory can no longer substitute a
221
+ * group key, which is what this function existed to be unable to say.
149
222
  */
150
223
  export function verifyInboundAssignment(assignment, expectedSignerHex) {
151
224
  if (assignment.signature_type !== "frost") {
@@ -159,9 +232,47 @@ export function verifyInboundAssignment(assignment, expectedSignerHex) {
159
232
  if (!signer || signer.length !== 32) {
160
233
  return { ok: false, reason: "inbound_assignment_no_signer", detail: "signer_pubkey missing or not 32 bytes" };
161
234
  }
162
- // PINNED beats named. When we hold an independent value, the frame's own claim about who signed is
163
- // not consulted at all using it would reintroduce the circularity the pin exists to break.
164
- const verifyAgainst = expectedSignerHex !== null ? Buffer.from(expectedSignerHex, "hex") : signer;
235
+ /**
236
+ * 038-KEYBINDPLACE THE GROUP KEY BEFORE VERIFYING ANYTHING UNDER IT.
237
+ *
238
+ * `participant_a.pubkey` is the counterparty's K_local: the 64-hex identity their operator was
239
+ * given out of band, and the value this daemon keys its contacts and its pin on. The binding is a
240
+ * signature under THAT key naming `signer_pubkey` as its group key.
241
+ *
242
+ * ABSENT IS A REFUSAL. Tolerating a missing binding would reproduce exactly the state this exists
243
+ * to end — and worse, it would hand the choice to the party the check exists to catch: a
244
+ * directory that cannot forge a binding simply omits one, and every session goes back to
245
+ * verifying a key against itself. The parser maps a malformed value to `undefined`, so missing
246
+ * and malformed take this same path.
247
+ */
248
+ const binding = assignment.participant_a_key_binding;
249
+ if (!binding) {
250
+ return {
251
+ ok: false,
252
+ reason: "inbound_assignment_no_key_binding",
253
+ detail: "the assignment carried no key binding for the initiator, so the threshold key it names could not be placed against their identity",
254
+ };
255
+ }
256
+ if (!verifyKeyBinding(binding, assignment.participant_a.pubkey, signer)) {
257
+ return {
258
+ ok: false,
259
+ reason: "inbound_assignment_key_binding_invalid",
260
+ detail: "the initiator's identity key has not signed for the threshold key this assignment names as theirs",
261
+ };
262
+ }
263
+ /**
264
+ * The key the BINDING proved — not the key the frame supplied.
265
+ *
266
+ * Textually these are the same bytes, and that is worth being precise about rather than glossing:
267
+ * what changed is that this value is now unreachable without a signature by `participant_a`'s
268
+ * K_local having verified over it three lines above. A directory that names a group key it holds
269
+ * cannot get here at all.
270
+ */
271
+ const boundGroupKey = signer;
272
+ // PINNED beats bound. When we hold a value from an earlier session, that is the stronger anchor —
273
+ // it survives the counterparty's own key material changing, which a fresh binding would not
274
+ // reveal on its own.
275
+ const verifyAgainst = expectedSignerHex !== null ? Buffer.from(expectedSignerHex, "hex") : boundGroupKey;
165
276
  if (expectedSignerHex !== null && Buffer.from(signer).toString("hex").toLowerCase() !== expectedSignerHex.toLowerCase()) {
166
277
  return {
167
278
  ok: false,
@@ -177,9 +288,14 @@ export function verifyInboundAssignment(assignment, expectedSignerHex) {
177
288
  reason: "inbound_assignment_signature_invalid",
178
289
  detail: expectedSignerHex !== null
179
290
  ? "the signature did not verify under the key this agent recorded for this counterparty"
180
- : "the signature did not verify over the assignment's own recomputed contents — the frame is tampered or malformed",
291
+ : "the signature did not verify under the threshold key the initiator's identity key vouched for — the frame is tampered or malformed",
181
292
  };
182
293
  }
183
- return { ok: true, mode: expectedSignerHex !== null ? "pinned" : "internal" };
294
+ // `bound` was `internal`, and the rename is the claim changing rather than the label.
295
+ // `internal` meant "internally consistent only — this proves nothing about the signer".
296
+ // A first contact now verifies under a group key the counterparty's own identity key signed
297
+ // for, so the old word would assert a weaker property than the code holds and send the next
298
+ // reader looking for a gap that has been closed.
299
+ return { ok: true, mode: expectedSignerHex !== null ? "pinned" : "bound" };
184
300
  }
185
301
  //# sourceMappingURL=assignment-verify.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"assignment-verify.js","sourceRoot":"","sources":["../src/assignment-verify.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"assignment-verify.js","sourceRoot":"","sources":["../src/assignment-verify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * CELLO Daemon — WHO SENT THIS, AND DID THEY SEE WHAT THEY CLAIM
3
+ *
4
+ * Split out of `session-node-manager.ts` by 036-GODFILE, Part 1. Two questions about one signed
5
+ * blob: did this really come from this conversation's counterparty, and is what they say they saw
6
+ * actually what we sent. Everything is moved verbatim — the comments are the asset and several of
7
+ * them record a defect that came back once already.
8
+ *
9
+ * ⚠️ THE ORDER OF THE CHECKS IS ITSELF A SECURITY PROPERTY — decode → signature → signer → what the
10
+ * proof is about. Everything after the signer answers `unusable`, which refuses the message and
11
+ * leaves the session alive; signature and signer answer `refuted`, which FREEZES it. A check that
12
+ * can answer before the signature is verified hands a peer a switch for choosing the softer
13
+ * outcome. Do not reorder these, and do not add a cheap early check above the signature.
14
+ *
15
+ * ⚠️ THIS CLASS OWNS ITS OWN STATE, which is what made it a seam rather than a split.
16
+ * `#receivedFromCounterparty` and `#lastFromCounterparty` are read and written nowhere else in the
17
+ * daemon, so they moved with the code that maintains them. Everything else it needs arrives through
18
+ * `AuthorshipContext` — a narrow, explicit list. No field of the manager was widened to make this
19
+ * possible, and the manager is not passed in as a whole.
20
+ */
21
+ import type { Logger, SessionRecord } from "./types.js";
22
+ import type { SessionTree } from "./session-tree.js";
23
+ import { type AuthorshipVerdict } from "./session-node-types.js";
24
+ /**
25
+ * Everything the verifier needs from the manager, stated explicitly rather than handed the manager
26
+ * itself. If this list ever grows long, that is the signal the seam has moved — not a reason to
27
+ * widen it.
28
+ */
29
+ export interface AuthorshipContext {
30
+ readonly logger: Logger;
31
+ sessionKey(agentName: string, sessionId: string): string;
32
+ getSessionRecord(agentName: string, sessionId: string): SessionRecord | null;
33
+ getSessionTree(agentName: string, sessionId: string): SessionTree;
34
+ isSessionDiverged(agentName: string, sessionId: string): boolean;
35
+ sessionGenesisPrevRoot(agentName: string, sessionId: string): Uint8Array | undefined;
36
+ /** The relay's session id for this session, when a live node holds one. */
37
+ relaySessionIdBytes(agentName: string, sessionId: string): Uint8Array | undefined;
38
+ /**
39
+ * Content received and verified but not yet appended, because it sits behind an ordering gap.
40
+ *
41
+ * ⚠️ RETURNS THE MAP, not a flattened list, and that is deliberate: the call site's expression
42
+ * (`[...heldHere.values()].some(...)`) moves across unchanged. Handing back a pre-computed array
43
+ * of hashes would read as a tidy-up and would be a behaviour change wearing a refactor's clothes.
44
+ */
45
+ heldContentFor(agentName: string, sessionId: string): ReadonlyMap<number, {
46
+ contentHashHex: string;
47
+ }> | undefined;
48
+ }
49
+ export declare class AuthorshipVerifier {
50
+ #private;
51
+ constructor(ctx: AuthorshipContext);
52
+ /**
53
+ * `DOD-M15-AUTHORSHIP-ABSENT-1` — DID THIS SENDER PROVE THEY WROTE THIS MESSAGE?
54
+ *
55
+ * The one place that answers it, for both callers, so "checked" cannot mean two different things
56
+ * in two places. It takes the signature as an ARGUMENT rather than digging it out of a structure,
57
+ * which is the whole of the fix: the signature used to be read only from `structure2_cbor` — the
58
+ * RELAY's record — so a receiver could not check authorship without a relay record, and refusing
59
+ * on its absence would have made the relay a precondition for reading mail. The content frame now
60
+ * carries the signature beside the bytes it signs, exactly as `hash_submit` always has, and this
61
+ * method does not care which of the two handed it over.
62
+ *
63
+ * It VERIFIES and it does not LOG. The severity of each verdict differs by caller — the content
64
+ * frame refuses an `unusable`, the park path shrugs at one — and a method that logged its own
65
+ * conclusion would either report a refusal that did not happen or stay silent on one that did.
66
+ */
67
+ verifyAuthorshipClaim(agentName: string, sessionId: string, structure1Cbor: Uint8Array, senderSignature: Uint8Array, contentHash: Uint8Array): AuthorshipVerdict;
68
+ noteReceivedFromCounterparty(agentName: string, sessionId: string, contentHash: Uint8Array): void;
69
+ }
70
+ //# sourceMappingURL=authorship-verification.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorship-verification.d.ts","sourceRoot":"","sources":["../src/authorship-verification.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrD,OAAO,EACL,KAAK,iBAAiB,EAQvB,MAAM,yBAAyB,CAAC;AAEjC;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IACzD,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAAC;IAC7E,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,CAAC;IAClE,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IACjE,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IACrF,2EAA2E;IAC3E,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IAClF;;;;;;OAMG;IACH,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,CAAC,MAAM,EAAE;QAAE,cAAc,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,SAAS,CAAC;CACnH;AAED,qBAAa,kBAAkB;;gBAGjB,GAAG,EAAE,iBAAiB;IAkBpC;;;;;;;;;;;;;;SAcK;IACH,qBAAqB,CACnB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,UAAU,EAC1B,eAAe,EAAE,UAAU,EAC3B,WAAW,EAAE,UAAU,GACtB,iBAAiB;IAgQtB,4BAA4B,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,GAAG,IAAI;CA0JhG"}