keri-ts 2.2.4 → 2.2.6

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 (256) hide show
  1. package/esm/keri/npm/src/_dnt.polyfills.js +14 -14
  2. package/esm/keri/npm/src/acdc/dhking.js +29 -2
  3. package/esm/keri/npm/src/acdc/observing.js +10 -3
  4. package/esm/keri/npm/src/acdc/query-resend.js +74 -0
  5. package/esm/keri/npm/src/acdc/regengine.js +39 -9
  6. package/esm/keri/npm/src/acdc/regeventing.js +5 -3
  7. package/esm/keri/npm/src/acdc/registering.js +19 -0
  8. package/esm/keri/npm/src/acdc/salting.js +4 -0
  9. package/esm/keri/npm/src/acdc/unblinding.js +38 -3
  10. package/esm/keri/npm/src/app/bootstrap/authenticating.js +32 -10
  11. package/esm/keri/npm/src/app/bootstrap/oobiery.js +363 -30
  12. package/esm/keri/npm/src/app/credentials/credential-importing.js +33 -4
  13. package/esm/keri/npm/src/app/credentials/root-replicator.js +27 -15
  14. package/esm/keri/npm/src/app/credentials/schema-resolving.js +5 -1
  15. package/esm/keri/npm/src/app/credentials/tel-publishing.js +16 -7
  16. package/esm/keri/npm/src/app/delivery/forwarding.js +205 -44
  17. package/esm/keri/npm/src/app/delivery/relaying.js +232 -0
  18. package/esm/keri/npm/src/app/delivery/respondant.js +8 -5
  19. package/esm/keri/npm/src/app/exchanges/exchange-messaging.js +1 -1
  20. package/esm/keri/npm/src/app/exchanges/exchanging.js +65 -9
  21. package/esm/keri/npm/src/app/identity/delegating.js +66 -9
  22. package/esm/keri/npm/src/app/identity/habbing.js +190 -71
  23. package/esm/keri/npm/src/app/identity/keeping.js +111 -39
  24. package/esm/keri/npm/src/app/identity/retire-identifier.js +98 -0
  25. package/esm/keri/npm/src/app/ipex/disclosure-replaying.js +116 -0
  26. package/esm/keri/npm/src/app/ipex/group-ipex.js +122 -25
  27. package/esm/keri/npm/src/app/ipex/group-issuance.js +137 -37
  28. package/esm/keri/npm/src/app/ipex/ipex-discloser.js +94 -31
  29. package/esm/keri/npm/src/app/ipex/ipex-grouping.js +53 -3
  30. package/esm/keri/npm/src/app/ipex/ipex-v2.js +7 -2
  31. package/esm/keri/npm/src/app/ipex/issuance.js +147 -44
  32. package/esm/keri/npm/src/app/ipex/pending-presentations.js +4 -2
  33. package/esm/keri/npm/src/app/ipex/presenting.js +3 -18
  34. package/esm/keri/npm/src/app/ipex/receive-issuance.js +2 -1
  35. package/esm/keri/npm/src/app/ipex/unblinding-workflow.js +39 -11
  36. package/esm/keri/npm/src/app/mailbox/agent-inbox.js +292 -0
  37. package/esm/keri/npm/src/app/mailbox/forward-handler.js +298 -52
  38. package/esm/keri/npm/src/app/mailbox/kel-publisher.js +26 -31
  39. package/esm/keri/npm/src/app/mailbox/mailbox-poller.js +70 -62
  40. package/esm/keri/npm/src/app/mailbox/mailboxing.js +20 -4
  41. package/esm/keri/npm/src/app/multisig/counseling.js +71 -27
  42. package/esm/keri/npm/src/app/multisig/group-members.js +41 -5
  43. package/esm/keri/npm/src/app/multisig/grouping.js +50 -2
  44. package/esm/keri/npm/src/app/multisig/multisig-workflows.js +3 -18
  45. package/esm/keri/npm/src/app/multisig/proposing.js +1 -16
  46. package/esm/keri/npm/src/app/multisig/rejoin-group.js +99 -0
  47. package/esm/keri/npm/src/app/multisig/replace-member.js +29 -8
  48. package/esm/keri/npm/src/app/protocol/parsering.js +45 -17
  49. package/esm/keri/npm/src/app/protocol/protocol-processing.js +47 -21
  50. package/esm/keri/npm/src/app/queries/query-replies.js +0 -22
  51. package/esm/keri/npm/src/app/queries/relay-refresh.js +81 -0
  52. package/esm/keri/npm/src/app/queries/witness-inquisitor.js +180 -34
  53. package/esm/keri/npm/src/app/runtime/agent-runtime.js +42 -73
  54. package/esm/keri/npm/src/app/runtime/cue-runtime.js +45 -5
  55. package/esm/keri/npm/src/app/runtime/ipex-auxiliaries.js +8 -1
  56. package/esm/keri/npm/src/app/runtime/operation-owners.js +26 -56
  57. package/esm/keri/npm/src/app/runtime/workflow-contract.js +7 -4
  58. package/esm/keri/npm/src/app/transport/http-client.js +20 -2
  59. package/esm/keri/npm/src/app/transport/message-source.js +2 -1
  60. package/esm/keri/npm/src/app/transport/messengering.js +39 -8
  61. package/esm/keri/npm/src/app/witness/ingress.js +7 -0
  62. package/esm/keri/npm/src/app/witness/receipt-client.js +43 -16
  63. package/esm/keri/npm/src/app/witness/receipting.js +62 -20
  64. package/esm/keri/npm/src/cli/messaging/multisig.js +4 -3
  65. package/esm/keri/npm/src/core/errors.js +73 -0
  66. package/esm/keri/npm/src/core/kel/delegation-ancestry.js +11 -2
  67. package/esm/keri/npm/src/core/kel/eventing.js +25 -28
  68. package/esm/keri/npm/src/core/kel/kever.js +15 -9
  69. package/esm/keri/npm/src/core/mailbox/kram-policy.js +16 -0
  70. package/esm/keri/npm/src/core/mailbox/kram-pruning.js +25 -6
  71. package/esm/keri/npm/src/core/mailbox/kram-time.js +34 -6
  72. package/esm/keri/npm/src/core/mailbox/kraming.js +58 -2
  73. package/esm/keri/npm/src/core/missing-anchor-query.js +20 -0
  74. package/esm/keri/npm/src/core/routing.js +18 -25
  75. package/esm/keri/npm/src/core/scheming.js +12 -5
  76. package/esm/keri/npm/src/core/wire-fields.js +126 -0
  77. package/esm/keri/npm/src/db/basing.js +4 -13
  78. package/esm/keri/npm/src/db/core/indexeddb-persistence.js +3 -2
  79. package/esm/keri/npm/src/db/core/webdber.js +85 -27
  80. package/esm/keri/npm/src/db/mailbox/mailboxing.js +13 -0
  81. package/esm/keri/npm/src/db/subing.js +4 -0
  82. package/esm/keri/npm/src/runtime/index.js +11 -5
  83. package/esm/keri/npm/src/time/mod.js +5 -0
  84. package/esm/keri/npm/src/vdr/credential-tsn-record.js +2 -7
  85. package/esm/keri/npm/src/vdr/eventing.js +71 -28
  86. package/esm/keri/npm/src/vdr/registry-tsn-record.js +3 -13
  87. package/esm/keri/npm/src/version.js +2 -2
  88. package/package.json +1 -1
  89. package/types/_dnt.polyfills.d.ts +9 -9
  90. package/types/_dnt.polyfills.d.ts.map +1 -1
  91. package/types/acdc/dhking.d.ts +8 -0
  92. package/types/acdc/dhking.d.ts.map +1 -1
  93. package/types/acdc/observing.d.ts +5 -1
  94. package/types/acdc/observing.d.ts.map +1 -1
  95. package/types/acdc/query-resend.d.ts +24 -0
  96. package/types/acdc/query-resend.d.ts.map +1 -0
  97. package/types/acdc/regengine.d.ts +19 -1
  98. package/types/acdc/regengine.d.ts.map +1 -1
  99. package/types/acdc/regeventing.d.ts.map +1 -1
  100. package/types/acdc/registering.d.ts +6 -0
  101. package/types/acdc/registering.d.ts.map +1 -1
  102. package/types/acdc/salting.d.ts.map +1 -1
  103. package/types/acdc/unblinding.d.ts +13 -1
  104. package/types/acdc/unblinding.d.ts.map +1 -1
  105. package/types/app/bootstrap/authenticating.d.ts +12 -2
  106. package/types/app/bootstrap/authenticating.d.ts.map +1 -1
  107. package/types/app/bootstrap/oobiery.d.ts +144 -7
  108. package/types/app/bootstrap/oobiery.d.ts.map +1 -1
  109. package/types/app/credentials/credential-importing.d.ts +8 -0
  110. package/types/app/credentials/credential-importing.d.ts.map +1 -1
  111. package/types/app/credentials/root-replicator.d.ts +19 -8
  112. package/types/app/credentials/root-replicator.d.ts.map +1 -1
  113. package/types/app/credentials/schema-resolving.d.ts.map +1 -1
  114. package/types/app/credentials/tel-publishing.d.ts +4 -2
  115. package/types/app/credentials/tel-publishing.d.ts.map +1 -1
  116. package/types/app/delivery/forwarding.d.ts +90 -5
  117. package/types/app/delivery/forwarding.d.ts.map +1 -1
  118. package/types/app/delivery/relaying.d.ts +144 -0
  119. package/types/app/delivery/relaying.d.ts.map +1 -0
  120. package/types/app/delivery/respondant.d.ts +9 -2
  121. package/types/app/delivery/respondant.d.ts.map +1 -1
  122. package/types/app/exchanges/exchanging.d.ts +36 -0
  123. package/types/app/exchanges/exchanging.d.ts.map +1 -1
  124. package/types/app/identity/delegating.d.ts +6 -0
  125. package/types/app/identity/delegating.d.ts.map +1 -1
  126. package/types/app/identity/habbing.d.ts +106 -19
  127. package/types/app/identity/habbing.d.ts.map +1 -1
  128. package/types/app/identity/keeping.d.ts +54 -19
  129. package/types/app/identity/keeping.d.ts.map +1 -1
  130. package/types/app/identity/retire-identifier.d.ts +24 -0
  131. package/types/app/identity/retire-identifier.d.ts.map +1 -0
  132. package/types/app/ipex/disclosure-replaying.d.ts +61 -0
  133. package/types/app/ipex/disclosure-replaying.d.ts.map +1 -0
  134. package/types/app/ipex/group-ipex.d.ts +60 -14
  135. package/types/app/ipex/group-ipex.d.ts.map +1 -1
  136. package/types/app/ipex/group-issuance.d.ts +54 -8
  137. package/types/app/ipex/group-issuance.d.ts.map +1 -1
  138. package/types/app/ipex/ipex-discloser.d.ts +39 -2
  139. package/types/app/ipex/ipex-discloser.d.ts.map +1 -1
  140. package/types/app/ipex/ipex-grouping.d.ts +23 -2
  141. package/types/app/ipex/ipex-grouping.d.ts.map +1 -1
  142. package/types/app/ipex/ipex-v2.d.ts +5 -2
  143. package/types/app/ipex/ipex-v2.d.ts.map +1 -1
  144. package/types/app/ipex/issuance.d.ts +46 -16
  145. package/types/app/ipex/issuance.d.ts.map +1 -1
  146. package/types/app/ipex/pending-presentations.d.ts +4 -2
  147. package/types/app/ipex/pending-presentations.d.ts.map +1 -1
  148. package/types/app/ipex/presenting.d.ts.map +1 -1
  149. package/types/app/ipex/receive-issuance.d.ts +2 -1
  150. package/types/app/ipex/receive-issuance.d.ts.map +1 -1
  151. package/types/app/ipex/unblinding-workflow.d.ts +13 -2
  152. package/types/app/ipex/unblinding-workflow.d.ts.map +1 -1
  153. package/types/app/mailbox/agent-inbox.d.ts +126 -0
  154. package/types/app/mailbox/agent-inbox.d.ts.map +1 -0
  155. package/types/app/mailbox/forward-handler.d.ts +114 -14
  156. package/types/app/mailbox/forward-handler.d.ts.map +1 -1
  157. package/types/app/mailbox/kel-publisher.d.ts +12 -22
  158. package/types/app/mailbox/kel-publisher.d.ts.map +1 -1
  159. package/types/app/mailbox/mailbox-poller.d.ts +40 -24
  160. package/types/app/mailbox/mailbox-poller.d.ts.map +1 -1
  161. package/types/app/mailbox/mailboxing.d.ts +19 -5
  162. package/types/app/mailbox/mailboxing.d.ts.map +1 -1
  163. package/types/app/multisig/counseling.d.ts +32 -11
  164. package/types/app/multisig/counseling.d.ts.map +1 -1
  165. package/types/app/multisig/group-members.d.ts +17 -1
  166. package/types/app/multisig/group-members.d.ts.map +1 -1
  167. package/types/app/multisig/grouping.d.ts +17 -1
  168. package/types/app/multisig/grouping.d.ts.map +1 -1
  169. package/types/app/multisig/multisig-workflows.d.ts +0 -8
  170. package/types/app/multisig/multisig-workflows.d.ts.map +1 -1
  171. package/types/app/multisig/proposing.d.ts +4 -19
  172. package/types/app/multisig/proposing.d.ts.map +1 -1
  173. package/types/app/multisig/rejoin-group.d.ts +119 -0
  174. package/types/app/multisig/rejoin-group.d.ts.map +1 -0
  175. package/types/app/multisig/replace-member.d.ts +15 -1
  176. package/types/app/multisig/replace-member.d.ts.map +1 -1
  177. package/types/app/protocol/parsering.d.ts +21 -2
  178. package/types/app/protocol/parsering.d.ts.map +1 -1
  179. package/types/app/protocol/protocol-processing.d.ts +25 -5
  180. package/types/app/protocol/protocol-processing.d.ts.map +1 -1
  181. package/types/app/queries/query-replies.d.ts +4 -8
  182. package/types/app/queries/query-replies.d.ts.map +1 -1
  183. package/types/app/queries/relay-refresh.d.ts +26 -0
  184. package/types/app/queries/relay-refresh.d.ts.map +1 -0
  185. package/types/app/queries/witness-inquisitor.d.ts +56 -5
  186. package/types/app/queries/witness-inquisitor.d.ts.map +1 -1
  187. package/types/app/runtime/agent-runtime.d.ts +34 -3
  188. package/types/app/runtime/agent-runtime.d.ts.map +1 -1
  189. package/types/app/runtime/cue-runtime.d.ts +15 -3
  190. package/types/app/runtime/cue-runtime.d.ts.map +1 -1
  191. package/types/app/runtime/ipex-auxiliaries.d.ts.map +1 -1
  192. package/types/app/runtime/operation-owners.d.ts +16 -26
  193. package/types/app/runtime/operation-owners.d.ts.map +1 -1
  194. package/types/app/runtime/workflow-contract.d.ts +21 -6
  195. package/types/app/runtime/workflow-contract.d.ts.map +1 -1
  196. package/types/app/transport/http-client.d.ts +9 -0
  197. package/types/app/transport/http-client.d.ts.map +1 -1
  198. package/types/app/transport/message-source.d.ts +6 -3
  199. package/types/app/transport/message-source.d.ts.map +1 -1
  200. package/types/app/transport/messengering.d.ts +10 -1
  201. package/types/app/transport/messengering.d.ts.map +1 -1
  202. package/types/app/witness/ingress.d.ts +1 -0
  203. package/types/app/witness/ingress.d.ts.map +1 -1
  204. package/types/app/witness/receipt-client.d.ts +19 -1
  205. package/types/app/witness/receipt-client.d.ts.map +1 -1
  206. package/types/app/witness/receipting.d.ts +24 -1
  207. package/types/app/witness/receipting.d.ts.map +1 -1
  208. package/types/cli/messaging/multisig.d.ts.map +1 -1
  209. package/types/core/cues.d.ts +53 -1
  210. package/types/core/cues.d.ts.map +1 -1
  211. package/types/core/errors.d.ts +32 -0
  212. package/types/core/errors.d.ts.map +1 -1
  213. package/types/core/kel/delegation-ancestry.d.ts.map +1 -1
  214. package/types/core/kel/eventing.d.ts.map +1 -1
  215. package/types/core/kel/kever-decisions.d.ts +3 -1
  216. package/types/core/kel/kever-decisions.d.ts.map +1 -1
  217. package/types/core/kel/kever.d.ts +7 -1
  218. package/types/core/kel/kever.d.ts.map +1 -1
  219. package/types/core/mailbox/kram-policy.d.ts +12 -0
  220. package/types/core/mailbox/kram-policy.d.ts.map +1 -1
  221. package/types/core/mailbox/kram-pruning.d.ts +5 -1
  222. package/types/core/mailbox/kram-pruning.d.ts.map +1 -1
  223. package/types/core/mailbox/kram-time.d.ts +26 -2
  224. package/types/core/mailbox/kram-time.d.ts.map +1 -1
  225. package/types/core/mailbox/kraming.d.ts +14 -1
  226. package/types/core/mailbox/kraming.d.ts.map +1 -1
  227. package/types/core/missing-anchor-query.d.ts +34 -0
  228. package/types/core/missing-anchor-query.d.ts.map +1 -0
  229. package/types/core/routing.d.ts +3 -7
  230. package/types/core/routing.d.ts.map +1 -1
  231. package/types/core/scheming.d.ts.map +1 -1
  232. package/types/core/wire-fields.d.ts +56 -0
  233. package/types/core/wire-fields.d.ts.map +1 -0
  234. package/types/db/basing.d.ts.map +1 -1
  235. package/types/db/core/indexeddb-persistence.d.ts +3 -2
  236. package/types/db/core/indexeddb-persistence.d.ts.map +1 -1
  237. package/types/db/core/webdber.d.ts +32 -6
  238. package/types/db/core/webdber.d.ts.map +1 -1
  239. package/types/db/exchange/exchange-records.d.ts +4 -0
  240. package/types/db/exchange/exchange-records.d.ts.map +1 -1
  241. package/types/db/mailbox/mailboxing.d.ts +10 -1
  242. package/types/db/mailbox/mailboxing.d.ts.map +1 -1
  243. package/types/db/subing.d.ts +2 -0
  244. package/types/db/subing.d.ts.map +1 -1
  245. package/types/runtime/index.d.ts +20 -14
  246. package/types/runtime/index.d.ts.map +1 -1
  247. package/types/time/mod.d.ts +2 -0
  248. package/types/time/mod.d.ts.map +1 -1
  249. package/types/vdr/credential-tsn-record.d.ts.map +1 -1
  250. package/types/vdr/eventing.d.ts +32 -1
  251. package/types/vdr/eventing.d.ts.map +1 -1
  252. package/types/vdr/registry-tsn-record.d.ts.map +1 -1
  253. package/types/version.d.ts +2 -2
  254. package/esm/keri/npm/src/app/runtime/signer-conditions.js +0 -51
  255. package/types/app/runtime/signer-conditions.d.ts +0 -32
  256. package/types/app/runtime/signer-conditions.d.ts.map +0 -1
@@ -1,3 +1,17 @@
1
+ // https://github.com/tc39/proposal-accessible-object-hasownproperty/blob/main/polyfill.js
2
+ if (!Object.hasOwn) {
3
+ Object.defineProperty(Object, "hasOwn", {
4
+ value: function (object, property) {
5
+ if (object == null) {
6
+ throw new TypeError("Cannot convert undefined or null to object");
7
+ }
8
+ return Object.prototype.hasOwnProperty.call(Object(object), property);
9
+ },
10
+ configurable: true,
11
+ enumerable: false,
12
+ writable: true,
13
+ });
14
+ }
1
15
  function findLastIndex(self, callbackfn, that) {
2
16
  const boundFunc = that === undefined ? callbackfn : callbackfn.bind(that);
3
17
  let index = self.length - 1;
@@ -34,18 +48,4 @@ if (!Uint8Array.prototype.findLast) {
34
48
  return findLast(this, callbackfn, that);
35
49
  };
36
50
  }
37
- // https://github.com/tc39/proposal-accessible-object-hasownproperty/blob/main/polyfill.js
38
- if (!Object.hasOwn) {
39
- Object.defineProperty(Object, "hasOwn", {
40
- value: function (object, property) {
41
- if (object == null) {
42
- throw new TypeError("Cannot convert undefined or null to object");
43
- }
44
- return Object.prototype.hasOwnProperty.call(Object(object), property);
45
- },
46
- configurable: true,
47
- enumerable: false,
48
- writable: true,
49
- });
50
- }
51
51
  export {};
@@ -17,6 +17,7 @@ import { query, reply } from "../core/protocol-eventing.js";
17
17
  import { messagize } from "../core/protocol-serialization.js";
18
18
  import { fetchStoredTsgs, unverifiedReplyDecision } from "../core/routing.js";
19
19
  import { timestampMicros } from "../time/mod.js";
20
+ import { QUERY_RESEND_MS } from "./query-resend.js";
20
21
  import { validateUpdate, verifyRegistryAnchor } from "./regeventing.js";
21
22
  import { publicOpeningAttachment } from "./registering.js";
22
23
  const domain = new TextEncoder().encode("KERI/ipex/dhke/X25519-HKDF-SHA256-Salt128/v1");
@@ -343,6 +344,8 @@ export class Dhke {
343
344
  peer: null,
344
345
  query: null,
345
346
  reply: null,
347
+ accepted: null,
348
+ repliedAt: 0,
346
349
  proposal: null,
347
350
  contribution: null,
348
351
  custody: null,
@@ -493,6 +496,12 @@ export class Dhke {
493
496
  if (own && (!op.proposal || !bytesEqual(op.proposal, serder.raw))) {
494
497
  throw new ValidationError("Own DH query lacks exact local proposal approval");
495
498
  }
499
+ // A group re-sends its frozen query unchanged (one member cannot re-date it), and KERIpy resends identical
500
+ // bytes. The wire, attachments included, is compared against the one already authenticated, so a copy is
501
+ // never trusted on its body or SAID alone.
502
+ const wire = auxiliaryQueryWire(envelope);
503
+ if (!own && op.accepted && bytesEqual(op.accepted, wire))
504
+ return this.duplicateQuery(op);
496
505
  if (!verifyQueryAttachments(this.hab.db, envelope)) {
497
506
  return { kind: "drop", reason: "missingAuthentication" };
498
507
  }
@@ -513,6 +522,7 @@ export class Dhke {
513
522
  }
514
523
  if (this.group && !this.custody(op)) {
515
524
  op.peer = { ...peer };
525
+ op.accepted = wire;
516
526
  return { kind: "accept" };
517
527
  }
518
528
  if (!op.reply) {
@@ -537,10 +547,12 @@ export class Dhke {
537
547
  }
538
548
  if (this.group) {
539
549
  this.outgoing.push({ kin: "sign", dest: this.hab.pre, msg: op.contribution.slice() });
550
+ op.accepted = wire;
540
551
  return { kind: "accept" };
541
552
  }
542
553
  }
543
- this.outgoing.push({ kin: "reply", dest: op.issuer, msg: op.reply.slice() });
554
+ this.emitReply(op);
555
+ op.accepted = wire;
544
556
  return { kind: "accept" };
545
557
  }
546
558
  catch (error) {
@@ -549,6 +561,21 @@ export class Dhke {
549
561
  throw error;
550
562
  }
551
563
  }
564
+ /**
565
+ * A copy of the accepted query: nothing is verified, derived or contributed again, so a member round that
566
+ * already holds its `sign` contribution is not asked for another. If the reply exists it is queued again for a
567
+ * peer that lost it, at most once per {@link QUERY_RESEND_MS}.
568
+ */
569
+ duplicateQuery(op) {
570
+ if (op.reply && this.now() - op.repliedAt >= QUERY_RESEND_MS)
571
+ this.emitReply(op);
572
+ return { kind: "accept" };
573
+ }
574
+ /** Queue the stored reply for the issuer and note when. */
575
+ emitReply(op) {
576
+ this.outgoing.push({ kin: "reply", dest: op.issuer, msg: op.reply.slice() });
577
+ op.repliedAt = this.now();
578
+ }
552
579
  /** Revery decides reply threshold acceptance before any issuer derivation or retention. */
553
580
  processReply({ serder, diger, route, cigars = [], tsgs = [] }) {
554
581
  try {
@@ -574,7 +601,7 @@ export class Dhke {
574
601
  });
575
602
  }
576
603
  if (this.custody(op))
577
- this.outgoing.push({ kin: "reply", dest: op.issuer, msg: op.reply.slice() });
604
+ this.emitReply(op);
578
605
  }
579
606
  else if (this.custody(op) && !op.completed)
580
607
  this.retain(op, peer, this.derive(op, peer, "issuer"));
@@ -11,9 +11,10 @@ import { publicOpeningAttachment } from "./registering.js";
11
11
  * event commits independently; a later failure preserves the already accepted prefix.
12
12
  * Parsed evidence stays borrowed. Composition supplies this owner to v2 protocol dispatch;
13
13
  * no local habitat, alias or application disclosure custody is created by public intake.
14
+ * `cues` receives the issuer KEL `logs` query of each event that first waits for its anchoring KEL event.
14
15
  */
15
16
  export class RegistryObserver {
16
- constructor(db, store) {
17
+ constructor(db, store, cues = null) {
17
18
  Object.defineProperty(this, "db", {
18
19
  enumerable: true,
19
20
  configurable: true,
@@ -26,6 +27,12 @@ export class RegistryObserver {
26
27
  writable: true,
27
28
  value: store
28
29
  });
30
+ Object.defineProperty(this, "cues", {
31
+ enumerable: true,
32
+ configurable: true,
33
+ writable: true,
34
+ value: cues
35
+ });
29
36
  }
30
37
  /** Stage parsed events before inception, otherwise delegate ordinary validation and acceptance. */
31
38
  processEvent(serder, locators = {}) {
@@ -62,7 +69,7 @@ export class RegistryObserver {
62
69
  if (event.ilk === Ilks.rip) {
63
70
  if (!event.issuer)
64
71
  return { kind: "reject", reason: "issuer" };
65
- engine ??= new RegistryEventer(this.db, event.issuer, this.store, event.regid);
72
+ engine ??= new RegistryEventer(this.db, event.issuer, this.store, event.regid, this.cues);
66
73
  }
67
74
  if (engine) {
68
75
  const decision = engine.processEvent(serder, locators);
@@ -124,6 +131,6 @@ export class RegistryObserver {
124
131
  if (result.kind !== "valid" || result.event.regid !== regid || !result.event.issuer) {
125
132
  throw new Error("Corrupt observer registry inception.");
126
133
  }
127
- return new RegistryEventer(this.db, result.event.issuer, this.store, regid);
134
+ return new RegistryEventer(this.db, result.event.issuer, this.store, regid, this.cues);
128
135
  }
129
136
  }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Resend schedule for a group's threshold DH or Unblind query.
3
+ *
4
+ * A single signer re-dates every resend (`Dhke.requery`, `Unblind.requery`), so a resend is a new query the peer's
5
+ * KRAM accepts. A group cannot: re-dating needs the members' signatures, so the frozen query is resent as the same
6
+ * bytes. The peer's KRAM drops identical copies cheaply only while its replay cache row lives, and once the query's
7
+ * `dt` is older than the multi-key acceptance window it refuses every copy as untimely. This schedule therefore
8
+ * backs off and stops resending an unchanged query when the peer can no longer accept it. It never ends the
9
+ * operation: the owner keeps waiting until its own deadline, exactly as before.
10
+ */
11
+ import { parseBytes } from "cesr-ts";
12
+ import { ValidationError } from "../core/errors.js";
13
+ import { timestampMicros } from "../time/mod.js";
14
+ /** Milliseconds between the first resends of a query, and the shortest gap a receiver re-sends a stored reply. */
15
+ export const QUERY_RESEND_MS = 1000;
16
+ /** The longest gap the backoff reaches, in milliseconds. */
17
+ export const QUERY_RESEND_CAP_MS = 8000;
18
+ /** One group operation's frozen threshold query and when it may next be sent; memory only. */
19
+ export class ThresholdQueryResend {
20
+ constructor() {
21
+ /** The frozen signed query, unchanged on every resend; absent until the members' contributions complete it. */
22
+ Object.defineProperty(this, "query", {
23
+ enumerable: true,
24
+ configurable: true,
25
+ writable: true,
26
+ value: void 0
27
+ });
28
+ Object.defineProperty(this, "queryAt", {
29
+ enumerable: true,
30
+ configurable: true,
31
+ writable: true,
32
+ value: 0
33
+ });
34
+ Object.defineProperty(this, "next", {
35
+ enumerable: true,
36
+ configurable: true,
37
+ writable: true,
38
+ value: 0
39
+ });
40
+ Object.defineProperty(this, "wait", {
41
+ enumerable: true,
42
+ configurable: true,
43
+ writable: true,
44
+ value: 0
45
+ });
46
+ }
47
+ /** Take the frozen query and restart the schedule; its own `dt` is what the acceptance window is measured from. */
48
+ set(query) {
49
+ const body = parseBytes(query)[0]?.body;
50
+ const micros = timestampMicros(body?.sad?.dt);
51
+ if (micros === null)
52
+ throw new ValidationError("Threshold query has no valid date");
53
+ this.query = query;
54
+ this.queryAt = Number(micros / 1000n);
55
+ this.next = 0;
56
+ this.wait = 0;
57
+ }
58
+ /** Whether a query is frozen and the running gap has elapsed at `now` (epoch ms). */
59
+ ready(now) {
60
+ return !!this.query && now >= this.next;
61
+ }
62
+ /**
63
+ * Whether the query is still younger than `windowMs` at `now`; past it a receiver can no longer accept the
64
+ * unchanged query, so it is not sent again. A null window (this store never ran KRAM) never closes.
65
+ */
66
+ fresh(now, windowMs) {
67
+ return windowMs === null || now - this.queryAt < windowMs;
68
+ }
69
+ /** Record a send at `now`: the next gap doubles from `baseMs` up to {@link QUERY_RESEND_CAP_MS}. */
70
+ sent(now, baseMs) {
71
+ this.wait = this.wait ? Math.max(baseMs, Math.min(this.wait * 2, QUERY_RESEND_CAP_MS)) : baseMs;
72
+ this.next = now + this.wait;
73
+ }
74
+ }
@@ -2,14 +2,18 @@
2
2
  import { Diger, Ilks, NumberPrimitive, } from "cesr-ts";
3
3
  import { DatabaseNotOpenError, DatabaseOperationError } from "../core/errors.js";
4
4
  import { consoleLogger } from "../core/logger.js";
5
+ import { missingAnchorCue, missingAnchorQuery } from "../core/missing-anchor-query.js";
5
6
  import { findRegistryAnchor, validateRegistrySourceLocators, validateRip, validateUpdate, verifyRegistryAnchor, } from "./regeventing.js";
6
7
  /**
7
8
  * Borrow the issuer's accepted KEL and registry store; no keys or handles are acquired.
8
9
  * The local facade and foreign observer share this accepted-state engine.
9
10
  * Publication is atomic; binding this instance happens only after successful commit.
11
+ * An event that first enters the anchor escrow publishes one issuer KEL `logs` query to `cues` after that write.
10
12
  */
11
13
  export class RegistryEventer {
12
- constructor(db, issuer, store, regk = null) {
14
+ constructor(db, issuer, store, regk = null,
15
+ /** Application cue flow for missing-anchor queries; null for a local issuer, whose own KEL holds every anchor. */
16
+ cues = null) {
13
17
  Object.defineProperty(this, "db", {
14
18
  enumerable: true,
15
19
  configurable: true,
@@ -34,6 +38,12 @@ export class RegistryEventer {
34
38
  writable: true,
35
39
  value: regk
36
40
  });
41
+ Object.defineProperty(this, "cues", {
42
+ enumerable: true,
43
+ configurable: true,
44
+ writable: true,
45
+ value: cues
46
+ });
37
47
  }
38
48
  /** Validated accepted head metadata; local construction may inspect it without duplicating acceptance checks. */
39
49
  get acceptedHeadEvent() {
@@ -234,6 +244,9 @@ export class RegistryEventer {
234
244
  const decision = this.processOne(serder, locators);
235
245
  if (decision.kind === "accept")
236
246
  this.processQueued();
247
+ // The escrow row is committed; ask the issuer's KEL holders for what the anchor needs through the ordinary cue path.
248
+ if (decision.kind === "escrow" && decision.query)
249
+ this.cues?.push(missingAnchorCue(decision.query));
237
250
  return decision;
238
251
  }
239
252
  /** Decide and persist one event; callers own subsequent chain progression. */
@@ -269,10 +282,8 @@ export class RegistryEventer {
269
282
  return previous;
270
283
  this.store.putEvent(serder);
271
284
  this.discoverAnchor(event);
272
- if (!this.store.anchor(event.said)) {
273
- this.store.escrowMissingAnchor(event.regid, Number(event.sn), event.said);
274
- return { kind: "escrow", reason: "anchor", event };
275
- }
285
+ if (!this.store.anchor(event.said))
286
+ return this.escrowAnchorless(event, locators);
276
287
  this.store.baser.maes.rem(event.regid, Number(event.sn), [event.said]);
277
288
  if (!prior) {
278
289
  this.store.escrowOutOfOrder(event.regid, Number(event.sn), event.said);
@@ -314,10 +325,8 @@ export class RegistryEventer {
314
325
  throw new Error("Registry head exists without its accepted inception.");
315
326
  this.store.putEvent(serder);
316
327
  this.discoverAnchor(event);
317
- if (!this.store.anchor(event.said)) {
318
- this.store.escrowMissingAnchor(event.regid, 0, event.said);
319
- return { kind: "escrow", reason: "anchor", event };
320
- }
328
+ if (!this.store.anchor(event.said))
329
+ return this.escrowAnchorless(event, locators);
321
330
  this.commit(event);
322
331
  return { kind: "accept", event };
323
332
  });
@@ -325,6 +334,27 @@ export class RegistryEventer {
325
334
  this.regk = event.regid;
326
335
  return decision;
327
336
  }
337
+ /**
338
+ * Retain an event whose anchoring issuer KEL event is not accepted yet and decide whether to ask for it.
339
+ *
340
+ * The decision names the missing KEL only when this call put the event into the anchor escrow; retries and repeat
341
+ * arrivals find the row present. A source seal naming this issuer supplies the anchor's sequence number, and the
342
+ * highest one bounds the replay; without one the query names only where the recipient's KEL ends.
343
+ */
344
+ escrowAnchorless(event, locators) {
345
+ const added = this.store.escrowMissingAnchor(event.regid, Number(event.sn), event.said);
346
+ const decision = { kind: "escrow", reason: "anchor", event };
347
+ if (!added)
348
+ return decision;
349
+ const seals = [
350
+ ...(locators.sscs ?? []).map((seal) => seal.s.num),
351
+ ...(locators.ssts ?? []).filter((seal) => seal.i.qb64 === this.issuer).map((seal) => seal.s.num),
352
+ ];
353
+ const anchor = seals.length ? seals.reduce((high, sn) => (sn > high ? sn : high)) : null;
354
+ const accepted = this.db.getKever(this.issuer, { refresh: true })?.sner.num ?? null;
355
+ const query = missingAnchorQuery(this.issuer, accepted, anchor);
356
+ return query ? { ...decision, query } : decision;
357
+ }
328
358
  /** Replay is read-only; first acceptance repeats this check under its write transaction. */
329
359
  acceptedEvent(event) {
330
360
  const existing = this.acceptedAt(event.regid, Number(event.sn));
@@ -150,8 +150,9 @@ function sealDigests(event) {
150
150
  export function findRegistryAnchor(db, issuer, registry) {
151
151
  let earliest = null;
152
152
  for (const event of currentRegistryBranch(db, issuer)) {
153
- if (db.fullyWitnessed(event)
154
- && event.seals.some((seal) => matchesRegistrySeal(seal, registry))) {
153
+ // Seal test first: fullyWitnessed walks back to the governing establishment, so it runs only on matches.
154
+ if (event.seals.some((seal) => matchesRegistrySeal(seal, registry))
155
+ && db.fullyWitnessed(event)) {
155
156
  earliest = [event.sn, event.said];
156
157
  }
157
158
  }
@@ -161,7 +162,8 @@ export function findRegistryAnchor(db, issuer, registry) {
161
162
  function findDigestAnchor(db, issuer, digest) {
162
163
  let earliest = null;
163
164
  for (const event of currentRegistryBranch(db, issuer)) {
164
- if (db.fullyWitnessed(event) && sealDigests(event).includes(digest)) {
165
+ // Seal test first: fullyWitnessed walks back to the governing establishment, so it runs only on matches.
166
+ if (sealDigests(event).includes(digest) && db.fullyWitnessed(event)) {
165
167
  earliest = [event.sn, event.said];
166
168
  }
167
169
  }
@@ -202,6 +202,25 @@ export class RegistryStore {
202
202
  const said = this.seqSaider(regk, sn);
203
203
  return said ? this.event(said.qb64) : null;
204
204
  }
205
+ /**
206
+ * Registry identifiers with an accepted head, in key order: a scan of `heds.`. With `issuer`, only those whose
207
+ * accepted inception (`rip` at ordinal 0) names that issuer. Local aliases (`regs.`) play no part, so this also
208
+ * lists registries a store learned from others, such as a group registry a co-member incepted.
209
+ */
210
+ registries(issuer) {
211
+ const found = [];
212
+ for (const [keys] of this.baser.heads.getTopItemIter()) {
213
+ const regk = String(keys[0]);
214
+ if (issuer === undefined) {
215
+ found.push(regk);
216
+ continue;
217
+ }
218
+ const rip = this.seqEvent(regk, 0);
219
+ if (rip?.ilk === Ilks.rip && rip.issuer === issuer)
220
+ found.push(regk);
221
+ }
222
+ return found;
223
+ }
205
224
  /** Publish caller-approved storage markers; validation and duplicate/head policy belong to the engine. */
206
225
  accept(regk, sn, serder) {
207
226
  sn = registryOrdinal(sn);
@@ -641,6 +641,10 @@ export class RegistryRoot {
641
641
  throw new RootUnverified(error.message, { cause: error });
642
642
  throw error;
643
643
  }
644
+ // The recipient's key opens the box; without its signer the copy stays pending until the host holds it.
645
+ if (!hab.mgr.hasSigner(hab.kever.verfers[0].qb64)) {
646
+ throw new RootUnverified("the recipient's signer is not held; the copy stays pending until it is");
647
+ }
644
648
  let plain;
645
649
  try {
646
650
  const opened = hab.mgr.open(essr, hab.kever.verfers[0]);
@@ -25,6 +25,7 @@ import { query, reply } from "../core/protocol-eventing.js";
25
25
  import { messagize } from "../core/protocol-serialization.js";
26
26
  import { fetchStoredTsgs, unverifiedReplyDecision } from "../core/routing.js";
27
27
  import { prepareNests, publishNests } from "../db/exchange/exchange-nests.js";
28
+ import { QUERY_RESEND_MS } from "./query-resend.js";
28
29
  import { validateUpdate, verifyRegistryAnchor, vet, vetBlind, vetBound, } from "./regeventing.js";
29
30
  import { publicOpeningAttachment } from "./registering.js";
30
31
  /** Ordinary registry states an Unblind operation may reveal unless its approval names others. */
@@ -89,7 +90,7 @@ const KEL_ASK_MS = 5000;
89
90
  */
90
91
  export class Unblind {
91
92
  /** Bind the exact Grant verifier's store; the state vocabulary is chosen per approved operation. */
92
- constructor({ hab, exc, registry, rvy = hab.rvy, kram = true }) {
93
+ constructor({ hab, exc, registry, rvy = hab.rvy, now = Date.now, kram = true }) {
93
94
  Object.defineProperty(this, "resource", {
94
95
  enumerable: true,
95
96
  configurable: true,
@@ -162,7 +163,14 @@ export class Unblind {
162
163
  writable: true,
163
164
  value: void 0
164
165
  });
166
+ Object.defineProperty(this, "now", {
167
+ enumerable: true,
168
+ configurable: true,
169
+ writable: true,
170
+ value: void 0
171
+ });
165
172
  this.kram = kram;
173
+ this.now = now;
166
174
  if (!(hab instanceof Hab) || hab.db !== exc.hby.db || registry.db !== hab.db || rvy.db !== hab.db
167
175
  || exc.hby.habs.get(hab.pre) !== hab) {
168
176
  throw new ValidationError("Unblind requires a local signing habitat");
@@ -228,6 +236,8 @@ export class Unblind {
228
236
  scope: new Map([...context.registries].map(([regid, nodes]) => [regid, [...nodes]])),
229
237
  query: null,
230
238
  reply: null,
239
+ accepted: null,
240
+ repliedAt: 0,
231
241
  requested: null,
232
242
  selected: null,
233
243
  openings: new Map(),
@@ -389,6 +399,11 @@ export class Unblind {
389
399
  if (own && (!op.proposal || !bytesEqual(op.proposal, serder.raw))) {
390
400
  throw new ValidationError("Own Unblind query lacks its exact locally approved proposal");
391
401
  }
402
+ // A group re-sends its frozen query unchanged (one member cannot re-date it), and KERIpy resends identical
403
+ // bytes. The wire, attachments and evidence included, is compared against the one already authenticated, so
404
+ // a copy is never trusted on its body or SAID alone.
405
+ if (!own && op.accepted && bytesEqual(op.accepted, this.queryWire(envelope)))
406
+ return this.duplicateQuery(op);
392
407
  if (!verifyQueryAttachments(this.hab.db, envelope))
393
408
  return { kind: "drop", reason: "missingAuthentication" };
394
409
  if (op.requested && !sameSelection(op.requested, selected)) {
@@ -429,9 +444,11 @@ export class Unblind {
429
444
  op.requestedByPeer = true;
430
445
  // KRAM now holds this query as seen, so a resend of it is a replay and a group cannot re-date it: the
431
446
  // owed reply must outlive this owner. The accepted request is the durable record; the operation is a cache.
432
- this.hab.db.heldu.pin([this.hab.pre, op.xid], auxiliaryQueryWire(envelope, this.publicEvents(envelope.nests)));
447
+ const wire = this.queryWire(envelope);
448
+ this.hab.db.heldu.pin([this.hab.pre, op.xid], wire);
433
449
  if (!this.group)
434
450
  this.respond(op.xid);
451
+ op.accepted = wire;
435
452
  return { kind: "accept" };
436
453
  }
437
454
  catch (error) {
@@ -533,9 +550,27 @@ export class Unblind {
533
550
  }
534
551
  op.reply = signed;
535
552
  }
536
- this.outgoing.push({ kin: "reply", dest: op.recipient, msg: op.reply.slice() });
553
+ this.emitReply(op);
537
554
  return op.reply.slice();
538
555
  }
556
+ /** The query as held and compared: its frame, every attachment group, the public evidence and KRAM's normalized proof. */
557
+ queryWire(envelope) {
558
+ return auxiliaryQueryWire(envelope, this.publicEvents(envelope.nests));
559
+ }
560
+ /**
561
+ * A copy of the accepted query: nothing is verified or derived again. If the reply exists it is queued again for
562
+ * a peer that lost it, at most once per {@link QUERY_RESEND_MS}.
563
+ */
564
+ duplicateQuery(op) {
565
+ if (op.reply && this.now() - op.repliedAt >= QUERY_RESEND_MS)
566
+ this.emitReply(op);
567
+ return { kind: "accept" };
568
+ }
569
+ /** Queue the stored reply for the recipient and note when. */
570
+ emitReply(op) {
571
+ this.outgoing.push({ kin: "reply", dest: op.recipient, msg: op.reply.slice() });
572
+ op.repliedAt = this.now();
573
+ }
539
574
  /** Independently vet openings before caching them; Revery owns signature threshold and BADA. */
540
575
  processReply({ serder, diger, route, cigars = [], tsgs = [], attachments }) {
541
576
  try {
@@ -2,7 +2,8 @@ import { call, scoped } from "effection";
2
2
  import { persistResolvedContact } from "../identity/organizing.js";
3
3
  import { doEveryOnWrite, PROTOCOL_PACING } from "../runtime/doing.js";
4
4
  import { systemClock } from "../runtime/runtime-clock.js";
5
- import { isWellKnownOobiUrl, parseOobiUrl } from "./oobiery.js";
5
+ import { isRequestableHttpUrl } from "../transport/http-client.js";
6
+ import { isWellKnownOobiUrl, oobiJobFailure, parseOobiUrl } from "./oobiery.js";
6
7
  /**
7
8
  * Return true when a well-known URL has been authorized into `wkas.`.
8
9
  *
@@ -118,23 +119,20 @@ export class Authenticator {
118
119
  return null;
119
120
  }
120
121
  /**
121
- * Fetch one `mfa.` record and persist the terminal auth result.
122
+ * Fetch one `mfa.` record and persist the terminal auth result. A URL the HTTP client refuses, a refused or failed
123
+ * request, or a non-OK status each end this authentication as failed in `rmfa.` and remove its `mfa.` row, so the
124
+ * next pass takes the next one; any other error is a fault and propagates.
122
125
  *
123
126
  * @param url Exact pending well-known authentication URL.
124
127
  * @param record Durable mfa record retained until response handling and cleanup establish terminal state.
125
128
  */
126
129
  *processPendingAuth(url, record) {
127
- // Only HTTP status is needed; the response owner still joins body cleanup.
128
130
  const cid = record.cid
129
131
  ?? parseOobiUrl(url, record.oobialias ?? undefined).cid
130
132
  ?? null;
131
- const http = this.http;
132
- const response = yield* scoped(function* () {
133
- const response = yield* http.open(url);
134
- return { ok: response.ok, status: response.status };
135
- });
133
+ const state = yield* this.authenticate(url);
136
134
  const date = new Date().toISOString();
137
- const state = response.ok ? "resolved" : `http-${response.status}`;
135
+ const ok = state === "resolved";
138
136
  this.hby.db.mfa.rem(url);
139
137
  this.hby.db.rmfa.pin(url, {
140
138
  ...record,
@@ -142,7 +140,7 @@ export class Authenticator {
142
140
  state,
143
141
  cid,
144
142
  });
145
- if (response.ok && cid) {
143
+ if (ok && cid) {
146
144
  this.hby.db.wkas.add(cid, { url, dt: date });
147
145
  persistResolvedContact(this.hby, cid, {
148
146
  alias: record.oobialias,
@@ -150,4 +148,28 @@ export class Authenticator {
150
148
  });
151
149
  }
152
150
  }
151
+ /**
152
+ * Request one well-known URL and read only its status; the response owner still joins body cleanup.
153
+ *
154
+ * @param url Pending well-known authentication URL.
155
+ * @returns The terminal state: `resolved` for an OK status, `http-<status>` otherwise, or the
156
+ * {@link OobiJobFailure} of a URL the client refuses or a refused or failed request.
157
+ */
158
+ *authenticate(url) {
159
+ if (!isRequestableHttpUrl(url))
160
+ return "url-refused";
161
+ const http = this.http;
162
+ try {
163
+ return yield* scoped(function* () {
164
+ const response = yield* http.open(url);
165
+ return response.ok ? "resolved" : `http-${response.status}`;
166
+ });
167
+ }
168
+ catch (error) {
169
+ const failure = oobiJobFailure(error);
170
+ if (failure === null)
171
+ throw error;
172
+ return failure;
173
+ }
174
+ }
153
175
  }