keri-ts 2.2.0 → 2.2.2

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 (132) hide show
  1. package/esm/keri/npm/src/_dnt.polyfills.js +1 -127
  2. package/esm/keri/npm/src/acdc/custody.js +23 -0
  3. package/esm/keri/npm/src/acdc/dhking.js +6 -1
  4. package/esm/keri/npm/src/acdc/salting.js +18 -0
  5. package/esm/keri/npm/src/acdc/scheming.js +31 -18
  6. package/esm/keri/npm/src/acdc/unblinding.js +112 -6
  7. package/esm/keri/npm/src/app/delivery/forwarding.js +9 -0
  8. package/esm/keri/npm/src/app/delivery/respondant.js +21 -0
  9. package/esm/keri/npm/src/app/identity/habbing.js +4 -1
  10. package/esm/keri/npm/src/app/identity/keeping.js +38 -1
  11. package/esm/keri/npm/src/app/ipex/group-ipex.js +14 -0
  12. package/esm/keri/npm/src/app/ipex/group-issuance.js +15 -9
  13. package/esm/keri/npm/src/app/ipex/ipex-building.js +2 -1
  14. package/esm/keri/npm/src/app/ipex/ipex-discloser.js +32 -8
  15. package/esm/keri/npm/src/app/ipex/ipex-grouping.js +16 -8
  16. package/esm/keri/npm/src/app/ipex/ipex-node-authentication.js +42 -18
  17. package/esm/keri/npm/src/app/ipex/issuance.js +136 -40
  18. package/esm/keri/npm/src/app/ipex/pending-presentations.js +50 -0
  19. package/esm/keri/npm/src/app/ipex/presenting.js +44 -2
  20. package/esm/keri/npm/src/app/ipex/unblinding-workflow.js +141 -30
  21. package/esm/keri/npm/src/app/mailbox/mailbox-poller.js +37 -9
  22. package/esm/keri/npm/src/app/mailbox/mailboxing.js +9 -3
  23. package/esm/keri/npm/src/app/multisig/counseling.js +83 -4
  24. package/esm/keri/npm/src/app/multisig/event-forwarding.js +27 -0
  25. package/esm/keri/npm/src/app/multisig/grouping.js +120 -1
  26. package/esm/keri/npm/src/app/multisig/multisig-messaging.js +33 -6
  27. package/esm/keri/npm/src/app/multisig/multisig-workflows.js +5 -9
  28. package/esm/keri/npm/src/app/multisig/proposals.js +1 -1
  29. package/esm/keri/npm/src/app/runtime/agent-runtime.js +43 -23
  30. package/esm/keri/npm/src/app/runtime/ipex-auxiliaries.js +59 -25
  31. package/esm/keri/npm/src/app/runtime/operation-owners.js +1 -0
  32. package/esm/keri/npm/src/app/runtime/signer-conditions.js +51 -0
  33. package/esm/keri/npm/src/app/transport/cesr-egress.js +6 -0
  34. package/esm/keri/npm/src/cli/credentials/ipex.js +7 -1
  35. package/esm/keri/npm/src/cli/messaging/group-join.js +7 -3
  36. package/esm/keri/npm/src/cli/messaging/multisig.js +37 -11
  37. package/esm/keri/npm/src/core/errors.js +91 -3
  38. package/esm/keri/npm/src/core/json-schema-meta.js +688 -0
  39. package/esm/keri/npm/src/core/json-schema.js +147 -0
  40. package/esm/keri/npm/src/core/kel/eventing.js +20 -2
  41. package/esm/keri/npm/src/core/scheming.js +27 -26
  42. package/esm/keri/npm/src/db/replay/tel-replay.js +6 -0
  43. package/esm/keri/npm/src/db/store-lock.js +12 -0
  44. package/esm/keri/npm/src/runtime/index.js +9 -8
  45. package/esm/keri/npm/src/runtime/web/output-barrier.js +9 -0
  46. package/esm/keri/npm/src/version.js +2 -2
  47. package/package.json +2 -2
  48. package/types/_dnt.polyfills.d.ts +0 -99
  49. package/types/_dnt.polyfills.d.ts.map +1 -1
  50. package/types/acdc/custody.d.ts +7 -0
  51. package/types/acdc/custody.d.ts.map +1 -1
  52. package/types/acdc/dhking.d.ts.map +1 -1
  53. package/types/acdc/salting.d.ts +2 -0
  54. package/types/acdc/salting.d.ts.map +1 -1
  55. package/types/acdc/scheming.d.ts +2 -0
  56. package/types/acdc/scheming.d.ts.map +1 -1
  57. package/types/acdc/unblinding.d.ts +16 -5
  58. package/types/acdc/unblinding.d.ts.map +1 -1
  59. package/types/app/delivery/forwarding.d.ts +1 -0
  60. package/types/app/delivery/forwarding.d.ts.map +1 -1
  61. package/types/app/delivery/respondant.d.ts +7 -0
  62. package/types/app/delivery/respondant.d.ts.map +1 -1
  63. package/types/app/identity/habbing.d.ts +10 -1
  64. package/types/app/identity/habbing.d.ts.map +1 -1
  65. package/types/app/identity/keeping.d.ts +18 -0
  66. package/types/app/identity/keeping.d.ts.map +1 -1
  67. package/types/app/ipex/group-ipex.d.ts +7 -0
  68. package/types/app/ipex/group-ipex.d.ts.map +1 -1
  69. package/types/app/ipex/group-issuance.d.ts +1 -1
  70. package/types/app/ipex/group-issuance.d.ts.map +1 -1
  71. package/types/app/ipex/ipex-building.d.ts.map +1 -1
  72. package/types/app/ipex/ipex-discloser.d.ts +31 -19
  73. package/types/app/ipex/ipex-discloser.d.ts.map +1 -1
  74. package/types/app/ipex/ipex-grouping.d.ts.map +1 -1
  75. package/types/app/ipex/ipex-node-authentication.d.ts +9 -1
  76. package/types/app/ipex/ipex-node-authentication.d.ts.map +1 -1
  77. package/types/app/ipex/issuance.d.ts +41 -8
  78. package/types/app/ipex/issuance.d.ts.map +1 -1
  79. package/types/app/ipex/pending-presentations.d.ts +62 -1
  80. package/types/app/ipex/pending-presentations.d.ts.map +1 -1
  81. package/types/app/ipex/presenting.d.ts +41 -4
  82. package/types/app/ipex/presenting.d.ts.map +1 -1
  83. package/types/app/ipex/unblinding-workflow.d.ts +46 -10
  84. package/types/app/ipex/unblinding-workflow.d.ts.map +1 -1
  85. package/types/app/mailbox/mailbox-poller.d.ts +11 -2
  86. package/types/app/mailbox/mailbox-poller.d.ts.map +1 -1
  87. package/types/app/mailbox/mailboxing.d.ts +4 -1
  88. package/types/app/mailbox/mailboxing.d.ts.map +1 -1
  89. package/types/app/multisig/counseling.d.ts +22 -1
  90. package/types/app/multisig/counseling.d.ts.map +1 -1
  91. package/types/app/multisig/event-forwarding.d.ts +6 -0
  92. package/types/app/multisig/event-forwarding.d.ts.map +1 -0
  93. package/types/app/multisig/grouping.d.ts +26 -0
  94. package/types/app/multisig/grouping.d.ts.map +1 -1
  95. package/types/app/multisig/multisig-messaging.d.ts +23 -6
  96. package/types/app/multisig/multisig-messaging.d.ts.map +1 -1
  97. package/types/app/multisig/multisig-workflows.d.ts.map +1 -1
  98. package/types/app/multisig/proposals.d.ts +3 -2
  99. package/types/app/multisig/proposals.d.ts.map +1 -1
  100. package/types/app/multisig/proposing.d.ts +8 -1
  101. package/types/app/multisig/proposing.d.ts.map +1 -1
  102. package/types/app/runtime/agent-runtime.d.ts +3 -0
  103. package/types/app/runtime/agent-runtime.d.ts.map +1 -1
  104. package/types/app/runtime/ipex-auxiliaries.d.ts +8 -3
  105. package/types/app/runtime/ipex-auxiliaries.d.ts.map +1 -1
  106. package/types/app/runtime/operation-owners.d.ts.map +1 -1
  107. package/types/app/runtime/signer-conditions.d.ts +32 -0
  108. package/types/app/runtime/signer-conditions.d.ts.map +1 -0
  109. package/types/app/transport/cesr-egress.d.ts +1 -0
  110. package/types/app/transport/cesr-egress.d.ts.map +1 -1
  111. package/types/cli/credentials/ipex.d.ts.map +1 -1
  112. package/types/cli/messaging/group-join.d.ts +4 -1
  113. package/types/cli/messaging/group-join.d.ts.map +1 -1
  114. package/types/cli/messaging/multisig.d.ts +1 -0
  115. package/types/cli/messaging/multisig.d.ts.map +1 -1
  116. package/types/core/errors.d.ts +20 -1
  117. package/types/core/errors.d.ts.map +1 -1
  118. package/types/core/json-schema-meta.d.ts +19 -0
  119. package/types/core/json-schema-meta.d.ts.map +1 -0
  120. package/types/core/json-schema.d.ts +29 -0
  121. package/types/core/json-schema.d.ts.map +1 -0
  122. package/types/core/scheming.d.ts +1 -0
  123. package/types/core/scheming.d.ts.map +1 -1
  124. package/types/db/replay/tel-replay.d.ts +1 -0
  125. package/types/db/replay/tel-replay.d.ts.map +1 -1
  126. package/types/db/store-lock.d.ts +2 -0
  127. package/types/db/store-lock.d.ts.map +1 -1
  128. package/types/runtime/index.d.ts +12 -11
  129. package/types/runtime/index.d.ts.map +1 -1
  130. package/types/runtime/web/output-barrier.d.ts +1 -0
  131. package/types/runtime/web/output-barrier.d.ts.map +1 -1
  132. package/types/version.d.ts +2 -2
@@ -23,6 +23,7 @@
23
23
  */
24
24
  import { concatBytes, Counter, Vrsn_2_0 } from "cesr-ts";
25
25
  import { ValidationError } from "../../core/errors.js";
26
+ import { loadNests } from "../../db/exchange/exchange-nests.js";
26
27
  import { registryEventStatus, settleRegistryEvent } from "../credentials/registry-settling.js";
27
28
  import { isLocalGroupHab } from "../multisig/group-members.js";
28
29
  import { MULTISIG_EXN_ROUTE } from "../multisig/grouping.js";
@@ -30,6 +31,8 @@ import { RetryBackoff } from "../runtime/retry-backoff.js";
30
31
  import { observeUntil } from "../runtime/workflow-contract.js";
31
32
  import { IpexAdmitter } from "./ipex-admitting.js";
32
33
  import { approveMultisigIpexLocally, approveStoredMultisigIpexWrappers, findStoredMultisigIpex, groupIpexApproved, prepareAcdcV2GroupIpex, publishGroupIpexProposal, publishInitialGroupIpex, requireAcdcV2GroupUse, sendCompletedGroupIpex, storedMultisigIpexBody, } from "./ipex-grouping.js";
34
+ import { isIssuanceOrigin } from "./ipex-node-authentication.js";
35
+ import { IPEX_GRANT_ROUTE } from "./ipexing.js";
33
36
  /** Milliseconds between collections of the other members' stored contributions. */
34
37
  export const GROUP_IPEX_COLLECT_MS = 250;
35
38
  /** How long delivery failures may last before an operation ends `failed`; a group IPEX message has no deadline. */
@@ -186,6 +189,7 @@ export class GroupIpexOperations {
186
189
  this.outcomes.set(op.said, {
187
190
  messages: op.publication?.messages ?? [],
188
191
  deliveries: op.publication?.deliveries ?? [],
192
+ members: op.publication?.members ?? [],
189
193
  sent: op.deliveries,
190
194
  });
191
195
  this.deps.changes?.push({ kind: "ipex", id: op.said });
@@ -257,6 +261,9 @@ export function groupIpexProposals(ops, hab) {
257
261
  continue;
258
262
  if (found.some((item) => item.said === embedded.said))
259
263
  continue;
264
+ if (embedded.route === IPEX_GRANT_ROUTE
265
+ && isIssuanceOrigin(group, loadNests(hby.db, wrapper.said)[0]?.nests?.[0]))
266
+ continue;
260
267
  found.push({
261
268
  said: embedded.said,
262
269
  route: embedded.route,
@@ -319,6 +326,13 @@ export function groupIpexStatus(ops, said) {
319
326
  }
320
327
  return { state: "active", phase: "awaitingConsent", needs: "approve" };
321
328
  }
329
+ /**
330
+ * The member wrappers this process has published for one message: from the live operation once it published, from
331
+ * the outcome after it ended. Empty before publication. A pure read.
332
+ */
333
+ export function groupIpexMessages(ops, said) {
334
+ return ops.live.get(said)?.publication?.messages ?? ops.outcomes.get(said)?.messages ?? [];
335
+ }
322
336
  /** Observe until the message is accepted and delivered, or ended, or the budget expires. Starts no worker. */
323
337
  export function* waitForGroupIpex(ops, said, { timeoutMs, clock = ops.deps.clock, until = (status) => status.state !== "active" }) {
324
338
  const settled = yield* observeUntil(() => until(groupIpexStatus(ops, said)), { timeoutMs, clock });
@@ -45,11 +45,12 @@ import { timestampMicros } from "../../time/mod.js";
45
45
  import { registryEventRef, registryEventStatus, settleRegistryEvent, } from "../credentials/registry-settling.js";
46
46
  import { groupElectedMember, groupSigningMembers, isLocalGroupHab, latestGroupContribution, localGroupMember, } from "../multisig/group-members.js";
47
47
  import { MULTISIG_EXN_ROUTE, MULTISIG_IPEX_PROOF_ROUTE, MULTISIG_QRY_ROUTE, MULTISIG_RPY_ROUTE, multisigExnBody, } from "../multisig/grouping.js";
48
+ import { remoteGroupMembers } from "../multisig/multisig-messaging.js";
48
49
  import { proposeRegistryEvent } from "../multisig/proposing.js";
49
50
  import { envelopeFromFrame } from "../protocol/parsering.js";
50
51
  import { exchangeWire } from "./ipex-discloser.js";
51
- import { acceptedRegistryEvidence } from "./ipex-node-authentication.js";
52
- import { ISSUANCE_QUERY_RESEND_MS, } from "./issuance.js";
52
+ import { acceptedRegistryEvidence, isIssuanceOrigin } from "./ipex-node-authentication.js";
53
+ import { ISSUANCE_QUERY_RESEND_MS, partyComplete, } from "./issuance.js";
53
54
  /** Milliseconds between scans of the stored wrappers for the final proof. */
54
55
  const PROOF_SCAN_MS = 1000;
55
56
  /** How many of the group's latest accepted events {@link deriveGroupCustody} considers as coordinators. */
@@ -119,9 +120,9 @@ export class GroupParty {
119
120
  get lead() {
120
121
  return this.member.pre === this.custodian;
121
122
  }
122
- /** The other current signing members of the group. */
123
+ /** The other current signing members of the group that are not held in this Habery; local ones share its stores. */
123
124
  others() {
124
- return groupSigningMembers(this.hby, this.group.pre).filter((peer) => peer !== this.member.pre);
125
+ return remoteGroupMembers(this.hby, this.member, groupSigningMembers(this.hby, this.group.pre));
125
126
  }
126
127
  /** The election this party stands for. */
127
128
  election() {
@@ -269,6 +270,8 @@ export function groupIssuanceProposals(ops, group) {
269
270
  ? child.pre !== group.pre
270
271
  : !hby.habs.get(child.pre) || !isLocalGroupHab(hby, hby.habs.get(child.pre))))
271
272
  continue;
273
+ if (!isIssuanceOrigin(child.pre, loadNests(hby.db, parent.said)[0]?.nests?.[0]))
274
+ continue;
272
275
  const self = localGroupMember(hby, child.pre).pre, known = found.get(child.said);
273
276
  if (known && (parent.pre === self || known.proposer !== self))
274
277
  continue;
@@ -324,7 +327,7 @@ function* proposalOf(ops, grant, proposal) {
324
327
  */
325
328
  export function* approveGroupIssuance(ops, grantSaid, options = {}) {
326
329
  const { deps, discloser } = ops;
327
- if (ops.live.has(grantSaid) || deps.exc.complete(grantSaid)) {
330
+ if (ops.liveOf(grantSaid, "issuer") || partyComplete(deps, grantSaid, "issuer")) {
328
331
  throw new ValidationError("Issuance was already begun for this Grant");
329
332
  }
330
333
  const { child } = yield* proposalOf(ops, grantSaid, options.proposal);
@@ -410,7 +413,7 @@ function pendingGrant(deps, said) {
410
413
  */
411
414
  export function approveGroupIssuanceExchange(ops, said, coordinator) {
412
415
  const { deps } = ops;
413
- if (ops.live.has(said) || deps.exc.complete(said)) {
416
+ if (ops.liveOf(said, "holder") || partyComplete(deps, said, "holder")) {
414
417
  throw new ValidationError("Issuance was already begun for this Grant");
415
418
  }
416
419
  const { grant, nodes } = pendingGrant(deps, said);
@@ -427,7 +430,7 @@ export function approveGroupIssuanceExchange(ops, said, coordinator) {
427
430
  */
428
431
  export function* followGroupIssuanceProof(ops, said, options = {}) {
429
432
  const { deps } = ops;
430
- if (ops.live.has(said) || deps.exc.complete(said)) {
433
+ if (ops.liveOf(said, "issuer") || partyComplete(deps, said, "issuer")) {
431
434
  throw new ValidationError("Issuance was already begun for this Grant");
432
435
  }
433
436
  const { grant, nodes } = pendingGrant(deps, said);
@@ -644,7 +647,7 @@ export function registerGroupRoutes(ops) {
644
647
  acceptsNests: true,
645
648
  verify: (args) => structural(deps.hby, args.serder, args.nests ?? []),
646
649
  handle: (args) => {
647
- for (const op of ops.live.values()) {
650
+ for (const op of ops.liveOps()) {
648
651
  if (op.group?.accepts(args.serder, args.nests)) {
649
652
  op.group.pending.set(args.serder.said, { wrapper: args.serder, child: args.nests[0] });
650
653
  }
@@ -758,7 +761,10 @@ export function* stepGroup(ops, op) {
758
761
  for (const peer of party.others())
759
762
  yield* send(peer, msg);
760
763
  };
761
- if (deps.exc.complete(grant))
764
+ // The accepted Grant is one fact of the Habery. A holder in the issuer's own Habery is not finished by the issuer
765
+ // finishing it: its key exchange completes, and its proof goes to its members, in its own steps.
766
+ const issuerHere = op.role === "holder" && deps.hby.habs.has(g.grant.pre);
767
+ if (deps.exc.complete(grant) && (!issuerHere || g.mode === "resumed"))
762
768
  return true;
763
769
  if (op.expires === null) {
764
770
  const deadline = deps.exc.exchangeDeadline(g.grant);
@@ -40,7 +40,8 @@ export function ipexExnBody(sender, route, nodes, options) {
40
40
  throw new Error("IPEX reply parties do not match the prior.");
41
41
  }
42
42
  if (verb === "spurn" && prior.route === "/ipex/grant" && prior.sad.p) {
43
- throw new Error("Only an opening grant may be spurned.");
43
+ throw new Error("Only a flow-starting Grant (one with no prior) may be spurned; a Grant that answers an Apply or Agree has no "
44
+ + "rejection message, so do not Admit it and keep it as evidence.");
44
45
  }
45
46
  recipient = prior.pre;
46
47
  xid = prior.ilk === "xip" ? prior.said : prior.sad.x;
@@ -25,6 +25,12 @@ import { ipexExnBody, prepareIpexExn } from "./ipex-building.js";
25
25
  import { walkIpexGraph } from "./ipex-graph.js";
26
26
  import { acceptedRegistryEvidence, authenticateIpexNode, issuedNodeVerdict } from "./ipex-node-authentication.js";
27
27
  import { ipexGrantWalk } from "./ipex-v2.js";
28
+ /** Refuse the builder's `prior` where the disclosure API names the prior exchange by `priorSaid`. */
29
+ function refuseBuilderPrior(options) {
30
+ if (options.prior !== undefined) {
31
+ throw new ValidationError("Name the prior exchange with priorSaid; prior is not a disclosure option.");
32
+ }
33
+ }
28
34
  /** Reuse a matching accepted KEL seal, or author once and gather real witness receipts. */
29
35
  export function* anchorExchange(hby, receiptor, hab, body, auths) {
30
36
  if (receiptor.hby !== hby)
@@ -61,8 +67,10 @@ export function exchangeWire(hab, exn, nested = new Uint8Array(), source, gvrsn
61
67
  return concatBytes(Counter.makeGVC(gvrsn), exn.raw, Counter.enclose({ qb64: concatBytes(proof, nested), version: gvrsn }));
62
68
  }
63
69
  /**
64
- * Re-encode a signed exchange from its retained, authenticated factors: the wire its signer sent, rebuilt
65
- * from the stored signatures or source seal and the stored nests. Signs nothing, so it needs no keys.
70
+ * Re-encode a signed exchange from its retained, authenticated factors: the exact bytes its signer presented,
71
+ * rebuilt from the stored signatures or source seal and the stored nests (`runtime.exc.retainedInput(said)` supplies
72
+ * them). Signs nothing, so it needs no keys. Public: a host that must hand the presented bytes to a third party
73
+ * reads them with this instead of re-signing.
66
74
  */
67
75
  export function* storedExchangeWire(retained, gvrsn = Vrsn_2_0) {
68
76
  const nests = [...retained.nests ?? []];
@@ -367,22 +375,30 @@ export class IpexDiscloser {
367
375
  }
368
376
  /** Create an application request; no credential-use approval is invented for negotiation. */
369
377
  *prepareApply(sender, options) {
370
- const hab = this.signer(sender, options.gvrsn ?? Vrsn_2_0), exn = ipexExnBody(sender, "/ipex/apply", [], options);
378
+ const hab = this.signer(sender, options.gvrsn ?? Vrsn_2_0), exn = this.applyBody(sender, options);
371
379
  return {
372
380
  exn,
373
381
  wire: exchangeWire(hab, exn, undefined, undefined, options.gvrsn ?? Vrsn_2_0),
374
382
  support: new Uint8Array(),
375
383
  };
376
384
  }
385
+ /** The unsigned Apply body, for a sender that signs elsewhere (a group's members each sign their share). */
386
+ applyBody(sender, options) {
387
+ return ipexExnBody(sender, "/ipex/apply", [], options);
388
+ }
377
389
  /** Accept or decline the exact authenticated predecessor and its negotiated anchoring choice. */
378
390
  *prepareReply(sender, verb, priorSaid, message = "", recipient, auths) {
379
- const hab = this.signer(sender), prior = yield* this.prior(priorSaid);
380
- const exn = ipexExnBody(sender, `/ipex/${verb}`, [], { message, prior, recipient });
391
+ const hab = this.signer(sender), exn = yield* this.replyBody(sender, verb, priorSaid, message, recipient);
381
392
  const source = verb === "agree" && exn.sad.a.ax?.[0]
382
393
  ? yield* anchorExchange(this.hby, this.receiptor, hab, exn, auths)
383
394
  : undefined;
384
395
  return { exn, wire: exchangeWire(hab, exn, undefined, source), support: source ? this.kel(hab) : new Uint8Array() };
385
396
  }
397
+ /** The unsigned Agree or Spurn body answering an accepted, authenticated predecessor. */
398
+ *replyBody(sender, verb, priorSaid, message = "", recipient) {
399
+ const prior = yield* this.prior(priorSaid);
400
+ return ipexExnBody(sender, `/ipex/${verb}`, [], { message, prior, recipient });
401
+ }
386
402
  /** Extend the frozen Grant's exact origin proof; ordinary intake still authenticates the supplied presentation evidence. */
387
403
  *attachPresentationProof(prepared, opening, source) {
388
404
  const frames = parseBytes(prepared.raw), node = frames[0].body;
@@ -463,15 +479,22 @@ export class IpexDiscloser {
463
479
  }
464
480
  /** Offer only the selected commitment/view without issuer proof attachments. */
465
481
  *prepareOffer(sender, selection, options) {
466
- const hab = this.signer(sender, options.gvrsn ?? Vrsn_2_0), prior = yield* this.prior(options.priorSaid);
467
- const raw = yield* this.selected(selection, true);
468
- const [exn, nests] = yield* prepareIpexExn(sender, "/ipex/offer", raw, { ...options, prior });
482
+ const hab = this.signer(sender, options.gvrsn ?? Vrsn_2_0);
483
+ const { exn, nests } = yield* this.prepareOfferBody(sender, selection, options);
469
484
  return {
470
485
  exn,
471
486
  wire: exchangeWire(hab, exn, nests, undefined, options.gvrsn ?? Vrsn_2_0),
472
487
  support: new Uint8Array(),
473
488
  };
474
489
  }
490
+ /** Freeze an Offer body and its nested evidence without signing, for a sender that signs elsewhere. */
491
+ *prepareOfferBody(sender, selection, options) {
492
+ refuseBuilderPrior(options);
493
+ const prior = yield* this.prior(options.priorSaid);
494
+ const raw = yield* this.selected(selection, true);
495
+ const [exn, nests] = yield* prepareIpexExn(sender, "/ipex/offer", raw, { ...options, prior });
496
+ return { exn, nests };
497
+ }
475
498
  /** Select and authorize once before freezing a Grant body, without signing or committing group approval. */
476
499
  *prepareGrantBody(sender, selection, options) {
477
500
  return yield* this.grantBody(sender, selection, options, false);
@@ -482,6 +505,7 @@ export class IpexDiscloser {
482
505
  }
483
506
  /** Both public preparation profiles preserve the same selected view and body construction. */
484
507
  *grantBody(sender, selection, options, pendingPresentation) {
508
+ refuseBuilderPrior(options);
485
509
  const hab = this.hby.habs.get(sender);
486
510
  if (!hab?.kever || (options.gvrsn ?? Vrsn_2_0).major < 2) {
487
511
  throw new ValidationError("ACDC v2 Grant preparation requires an accepted local sender and attachment genus 2.");
@@ -36,15 +36,20 @@ function acdcV2GraphUse(hby, registry, origin, nests, walk) {
36
36
  const graph = walkIpexGraph(origin, nests, walk);
37
37
  if (!graph)
38
38
  throw new ValidationError("ACDC v2 group graph is malformed or incomplete.");
39
- const verdicts = new Map();
39
+ const verdicts = new Map(), why = [];
40
40
  for (const [said, node] of graph.nodes) {
41
- verdicts.set(said, issuedNodeVerdict(authenticateIpexNode(node.body, envelopeFromFrame(node), hby.db, registry)));
41
+ const authentication = authenticateIpexNode(node.body, envelopeFromFrame(node), hby.db, registry);
42
+ verdicts.set(said, issuedNodeVerdict(authentication));
43
+ if (authentication.kind !== "authenticated")
44
+ why.push(`${said} ${authentication.kind}: ${authentication.reason}`);
42
45
  }
43
46
  const verdict = evaluateChain(graph, verdicts, hby.db);
44
47
  if (verdict?.kind === "unknown" && verdict.retryable)
45
48
  return null;
46
- if (verdict?.kind !== "valid")
47
- throw new ValidationError("ACDC v2 group graph is false or unresolved.");
49
+ if (verdict?.kind !== "valid") {
50
+ const found = !verdict ? "no verdict" : verdict.kind === "unknown" ? `unknown: ${verdict.reason}` : verdict.kind;
51
+ throw new ValidationError(`ACDC v2 group graph is false or unresolved (${found}${why.length ? `; ${why.join("; ")}` : ""}).`);
52
+ }
48
53
  return true;
49
54
  }
50
55
  /** Current retained Grant authentication and graph truth are separate from protocol completion/history. */
@@ -176,12 +181,13 @@ export function* approveMultisigIpex(hby, protocol, postman, reger, wrapper, opt
176
181
  const local = yield* approveMultisigIpexLocally(hby, protocol, reger, wrapper);
177
182
  const { embedded, groupHab, genus, approved } = local;
178
183
  const embeddedSaid = embedded.said;
179
- if (!approved)
180
- return { approved: null, accepted: protocol.exc.complete(embeddedSaid), messages: [], deliveries: [] };
184
+ if (!approved) {
185
+ return { approved: null, accepted: protocol.exc.complete(embeddedSaid), messages: [], deliveries: [], members: [] };
186
+ }
181
187
  const publication = options.publish
182
188
  ? yield* publishGroupIpexProposal(hby, protocol, postman, groupHab, approved, genus)
183
- : { messages: [], deliveries: [] };
184
- const deliveries = publication.deliveries;
189
+ : { messages: [], deliveries: [], members: [] };
190
+ const deliveries = [...publication.deliveries];
185
191
  if ((options.sendLead ?? true)
186
192
  && hby.db.exns.get([embeddedSaid])?.said === embeddedSaid
187
193
  && protocol.exc.lead(groupHab, embeddedSaid)) {
@@ -191,6 +197,7 @@ export function* approveMultisigIpex(hby, protocol, postman, reger, wrapper, opt
191
197
  accepted: hby.db.exns.get([embeddedSaid])?.said === embeddedSaid,
192
198
  messages: publication.messages,
193
199
  deliveries,
200
+ members: publication.members,
194
201
  approved,
195
202
  };
196
203
  }
@@ -430,6 +437,7 @@ export function* publishInitialGroupIpex(hby, protocol, postman, groupHab, embed
430
437
  recipient: message.recipient,
431
438
  exnSaid: message.exnSaid,
432
439
  payloadSaid: message.payloadSaid,
440
+ outcome: "delivered",
433
441
  deliveries: outcome.deliveries,
434
442
  });
435
443
  }
@@ -1,9 +1,10 @@
1
1
  /** Node issuer authentication over public KEL/TEL evidence; no conversation or validity policy. */
2
- import { Diger, NumberPrimitive, Prefixer } from "cesr-ts";
2
+ import { Diger, NumberPrimitive, Prefixer, SerderACDC } from "cesr-ts";
3
3
  import { validateRip, validateUpdate, vetBinds, vetBlind, vetBound, } from "../../acdc/regeventing.js";
4
4
  import { TransIdxSigGroup } from "../../core/dispatch.js";
5
5
  import { DatabaseOperationError } from "../../core/errors.js";
6
6
  import { verifyExchangeProofs } from "../../core/exchange/exchange-proofing.js";
7
+ import { envelopeFromFrame } from "../protocol/parsering.js";
7
8
  /** Protocol admission authenticates registry observations without imposing an application state vocabulary. */
8
9
  export function authenticatedNodeVerdict(authentication) {
9
10
  switch (authentication.kind) {
@@ -65,35 +66,57 @@ export function acceptedRegistryEvidence(store, regid) {
65
66
  }
66
67
  return { rip, updates };
67
68
  }
69
+ /**
70
+ * Whether a Grant's origin node is a fresh issuance: a credential its own sender issues, disclosed with no registry
71
+ * proof because no registry update has issued it yet. A presentation of an issued credential carries the proof
72
+ * that opens its registry update, so it never matches. Both are Grants on the wire; only a fresh one is consented to
73
+ * as an issuance. The answer is the same before and after the registry update is approved.
74
+ */
75
+ export function isIssuanceOrigin(sender, origin) {
76
+ const body = origin?.body;
77
+ if (!(body instanceof SerderACDC) || body.issuer !== sender || !body.regid)
78
+ return false;
79
+ const { bsqs, bsss } = envelopeFromFrame(origin);
80
+ return !bsqs.length && !bsss.length;
81
+ }
68
82
  /** A declared registry, then supplied anchors, take precedence over current signatures. */
69
83
  export function authenticateIpexNode(serder, envelope, db, registry, historical = false) {
70
84
  if (serder.regid) {
71
85
  const proofs = [...envelope.bsqs, ...envelope.bsss];
72
- if (!proofs.length || !registry)
73
- return { kind: "reject" };
86
+ if (!proofs.length || !registry) {
87
+ return {
88
+ kind: "reject",
89
+ reason: "The node declares a registry but carries no issuer proof or no registry store is open.",
90
+ };
91
+ }
74
92
  const chain = acceptedRegistryEvidence(registry, serder.regid);
75
93
  if (!chain)
76
94
  return { kind: "missing", reason: `Missing local TEL evidence for ${serder.regid}.` };
77
95
  // A RIP alone authenticates ownership, never a disclosed credential state.
78
- if (!chain.updates.length)
79
- return { kind: "reject" };
96
+ if (!chain.updates.length) {
97
+ return {
98
+ kind: "reject",
99
+ reason: "The registry has only its inception; a RIP alone authenticates ownership, never a disclosed credential state.",
100
+ };
101
+ }
80
102
  let at;
81
103
  if (historical) {
82
104
  if (!serder.verify() || serder.issuer !== chain.rip.issuer || serder.regid !== chain.rip.said) {
83
- return { kind: "reject" };
105
+ return { kind: "reject", reason: "The node differs from the registry inception it declares." };
84
106
  }
85
107
  // Authenticate all supplied bodies/duplicates and known bound contradictions
86
108
  // before missing evidence wins, independently of attachment order.
87
109
  if (proofs.some((proof) => !vetBlind(proof, proof.d.nonce))
88
110
  || new Set(proofs.map((proof) => proof.d.nonce)).size !== proofs.length)
89
- return { kind: "reject" };
111
+ return { kind: "reject", reason: "The disclosed issuer proofs are malformed or repeat a nonce." };
90
112
  let unresolved = false;
91
113
  for (const proof of proofs) {
92
114
  if (proof.td.nonce && proof.td.nonce !== serder.said)
93
115
  continue;
94
116
  const bound = vetBound(proof, serder, db);
95
- if (bound?.kind === "reject")
96
- return { kind: "reject" };
117
+ if (bound?.kind === "reject") {
118
+ return { kind: "reject", reason: "A disclosed issuer proof contradicts the node." };
119
+ }
97
120
  if (bound)
98
121
  unresolved = true;
99
122
  }
@@ -103,7 +126,7 @@ export function authenticateIpexNode(serder, envelope, db, registry, historical
103
126
  continue;
104
127
  const matches = chain.updates.filter((event) => event.sad.b === proof.d.nonce);
105
128
  if (matches.length > 1)
106
- return { kind: "reject" };
129
+ return { kind: "reject", reason: "Several registry updates match one issuer proof." };
107
130
  if (matches.length)
108
131
  selected.push(matches[0]);
109
132
  else
@@ -111,8 +134,9 @@ export function authenticateIpexNode(serder, envelope, db, registry, historical
111
134
  }
112
135
  if (unresolved)
113
136
  return { kind: "missing", reason: `Missing retained issuer proof evidence for ${serder.said}.` };
114
- if (!selected.length)
115
- return { kind: "reject" };
137
+ if (!selected.length) {
138
+ return { kind: "reject", reason: "No registry update matches the disclosed issuer proofs." };
139
+ }
116
140
  const candidates = new Set(selected.map((event) => event.said));
117
141
  // Accepted history is ascending. Vacuous and other-target openings cannot
118
142
  // advance this issuer prefix; generic source locators never select it.
@@ -131,7 +155,7 @@ export function authenticateIpexNode(serder, envelope, db, registry, historical
131
155
  }
132
156
  return result.kind === "verified" && result.state.regid === serder.regid
133
157
  ? { kind: "authenticated", registration: result.state }
134
- : { kind: "reject" };
158
+ : { kind: "reject", reason: "The registry update does not bind the node." };
135
159
  }
136
160
  // Disclosed inner rd without top-level rd is an unsupported hidden issuer registry.
137
161
  // Compact aggregate entries expose no declaration and remain opaque here.
@@ -139,15 +163,15 @@ export function authenticateIpexNode(serder, envelope, db, registry, historical
139
163
  const sections = [serder.sad.a, ...(Array.isArray(aggregate) ? aggregate.slice(1) : [aggregate])];
140
164
  if (sections.some(section => section && typeof section === "object" && !Array.isArray(section)
141
165
  && section.rd))
142
- return { kind: "reject" };
166
+ return { kind: "reject", reason: "A disclosed inner registry has no top-level declaration." };
143
167
  if (!serder.issuer)
144
- return { kind: "reject" };
168
+ return { kind: "reject", reason: "The node names no issuer." };
145
169
  let issuer;
146
170
  try {
147
171
  issuer = new Prefixer({ qb64: serder.issuer });
148
172
  }
149
173
  catch {
150
- return { kind: "reject" };
174
+ return { kind: "reject", reason: "The node names a malformed issuer." };
151
175
  }
152
176
  const sourceSeals = [
153
177
  ...envelope.sscs.map((seal) => ({ i: issuer, ...seal })),
@@ -159,7 +183,7 @@ export function authenticateIpexNode(serder, envelope, db, registry, historical
159
183
  ? { kind: "missing", reason: `Missing issuer KEL anchor for ${serder.said}.` }
160
184
  : result.sourceSeals.length > 0
161
185
  ? { kind: "authenticated", registration: null }
162
- : { kind: "reject" };
186
+ : { kind: "reject", reason: "No issuer KEL anchor seals the node." };
163
187
  }
164
188
  const explicit = envelope.tsgs.filter((group) => group.pre === issuer.qb64);
165
189
  const last = envelope.ssgs.filter((group) => group.pre === issuer.qb64);
@@ -181,5 +205,5 @@ export function authenticateIpexNode(serder, envelope, db, registry, historical
181
205
  ? { kind: "missing", reason: `Missing current issuer KEL evidence for ${serder.said}.` }
182
206
  : result.tsgs.length + result.cigars.length > 0
183
207
  ? { kind: "authenticated", registration: null }
184
- : { kind: "reject" };
208
+ : { kind: "reject", reason: "No current issuer signature authenticates the node." };
185
209
  }