keri-ts 2.2.1 → 2.2.3

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 (115) hide show
  1. package/esm/keri/npm/src/_dnt.polyfills.js +1 -127
  2. package/esm/keri/npm/src/acdc/dhking.js +6 -1
  3. package/esm/keri/npm/src/acdc/salting.js +18 -0
  4. package/esm/keri/npm/src/acdc/scheming.js +31 -18
  5. package/esm/keri/npm/src/acdc/unblinding.js +112 -6
  6. package/esm/keri/npm/src/app/delivery/forwarding.js +9 -0
  7. package/esm/keri/npm/src/app/delivery/respondant.js +21 -0
  8. package/esm/keri/npm/src/app/identity/delegating.js +2 -5
  9. package/esm/keri/npm/src/app/identity/habbing.js +52 -6
  10. package/esm/keri/npm/src/app/identity/keeping.js +38 -1
  11. package/esm/keri/npm/src/app/ipex/group-issuance.js +9 -6
  12. package/esm/keri/npm/src/app/ipex/ipex-building.js +2 -1
  13. package/esm/keri/npm/src/app/ipex/ipex-discloser.js +32 -8
  14. package/esm/keri/npm/src/app/ipex/issuance.js +142 -43
  15. package/esm/keri/npm/src/app/ipex/pending-presentations.js +50 -0
  16. package/esm/keri/npm/src/app/ipex/presenting.js +36 -1
  17. package/esm/keri/npm/src/app/ipex/unblinding-workflow.js +150 -36
  18. package/esm/keri/npm/src/app/mailbox/mailbox-poller.js +37 -9
  19. package/esm/keri/npm/src/app/mailbox/mailboxing.js +9 -3
  20. package/esm/keri/npm/src/app/multisig/counseling.js +76 -3
  21. package/esm/keri/npm/src/app/multisig/event-forwarding.js +27 -0
  22. package/esm/keri/npm/src/app/multisig/group-members.js +22 -0
  23. package/esm/keri/npm/src/app/multisig/grouping.js +120 -1
  24. package/esm/keri/npm/src/app/multisig/proposals.js +1 -1
  25. package/esm/keri/npm/src/app/multisig/replace-member.js +34 -0
  26. package/esm/keri/npm/src/app/runtime/agent-runtime.js +43 -23
  27. package/esm/keri/npm/src/app/runtime/ipex-auxiliaries.js +59 -25
  28. package/esm/keri/npm/src/app/runtime/operation-owners.js +1 -0
  29. package/esm/keri/npm/src/app/runtime/signer-conditions.js +51 -0
  30. package/esm/keri/npm/src/app/transport/cesr-egress.js +6 -0
  31. package/esm/keri/npm/src/core/errors.js +91 -3
  32. package/esm/keri/npm/src/core/json-schema-meta.js +688 -0
  33. package/esm/keri/npm/src/core/json-schema.js +147 -0
  34. package/esm/keri/npm/src/core/kel/eventing.js +20 -2
  35. package/esm/keri/npm/src/core/scheming.js +27 -26
  36. package/esm/keri/npm/src/db/replay/tel-replay.js +6 -0
  37. package/esm/keri/npm/src/db/store-lock.js +12 -0
  38. package/esm/keri/npm/src/runtime/index.js +9 -7
  39. package/esm/keri/npm/src/runtime/web/output-barrier.js +9 -0
  40. package/esm/keri/npm/src/version.js +2 -2
  41. package/package.json +2 -2
  42. package/types/_dnt.polyfills.d.ts +0 -99
  43. package/types/_dnt.polyfills.d.ts.map +1 -1
  44. package/types/acdc/dhking.d.ts.map +1 -1
  45. package/types/acdc/salting.d.ts +2 -0
  46. package/types/acdc/salting.d.ts.map +1 -1
  47. package/types/acdc/scheming.d.ts +2 -0
  48. package/types/acdc/scheming.d.ts.map +1 -1
  49. package/types/acdc/unblinding.d.ts +16 -5
  50. package/types/acdc/unblinding.d.ts.map +1 -1
  51. package/types/app/delivery/forwarding.d.ts +1 -0
  52. package/types/app/delivery/forwarding.d.ts.map +1 -1
  53. package/types/app/delivery/respondant.d.ts +7 -0
  54. package/types/app/delivery/respondant.d.ts.map +1 -1
  55. package/types/app/identity/delegating.d.ts.map +1 -1
  56. package/types/app/identity/habbing.d.ts +21 -1
  57. package/types/app/identity/habbing.d.ts.map +1 -1
  58. package/types/app/identity/keeping.d.ts +18 -0
  59. package/types/app/identity/keeping.d.ts.map +1 -1
  60. package/types/app/ipex/group-issuance.d.ts.map +1 -1
  61. package/types/app/ipex/ipex-building.d.ts.map +1 -1
  62. package/types/app/ipex/ipex-discloser.d.ts +31 -19
  63. package/types/app/ipex/ipex-discloser.d.ts.map +1 -1
  64. package/types/app/ipex/issuance.d.ts +46 -10
  65. package/types/app/ipex/issuance.d.ts.map +1 -1
  66. package/types/app/ipex/pending-presentations.d.ts +62 -1
  67. package/types/app/ipex/pending-presentations.d.ts.map +1 -1
  68. package/types/app/ipex/presenting.d.ts +39 -4
  69. package/types/app/ipex/presenting.d.ts.map +1 -1
  70. package/types/app/ipex/unblinding-workflow.d.ts +51 -12
  71. package/types/app/ipex/unblinding-workflow.d.ts.map +1 -1
  72. package/types/app/mailbox/mailbox-poller.d.ts +11 -2
  73. package/types/app/mailbox/mailbox-poller.d.ts.map +1 -1
  74. package/types/app/mailbox/mailboxing.d.ts +4 -1
  75. package/types/app/mailbox/mailboxing.d.ts.map +1 -1
  76. package/types/app/multisig/counseling.d.ts +22 -1
  77. package/types/app/multisig/counseling.d.ts.map +1 -1
  78. package/types/app/multisig/event-forwarding.d.ts +6 -0
  79. package/types/app/multisig/event-forwarding.d.ts.map +1 -0
  80. package/types/app/multisig/group-members.d.ts +6 -0
  81. package/types/app/multisig/group-members.d.ts.map +1 -1
  82. package/types/app/multisig/grouping.d.ts +26 -0
  83. package/types/app/multisig/grouping.d.ts.map +1 -1
  84. package/types/app/multisig/proposals.d.ts +3 -2
  85. package/types/app/multisig/proposals.d.ts.map +1 -1
  86. package/types/app/multisig/proposing.d.ts +8 -1
  87. package/types/app/multisig/proposing.d.ts.map +1 -1
  88. package/types/app/multisig/replace-member.d.ts +30 -0
  89. package/types/app/multisig/replace-member.d.ts.map +1 -0
  90. package/types/app/runtime/agent-runtime.d.ts +3 -0
  91. package/types/app/runtime/agent-runtime.d.ts.map +1 -1
  92. package/types/app/runtime/ipex-auxiliaries.d.ts +8 -3
  93. package/types/app/runtime/ipex-auxiliaries.d.ts.map +1 -1
  94. package/types/app/runtime/operation-owners.d.ts.map +1 -1
  95. package/types/app/runtime/signer-conditions.d.ts +32 -0
  96. package/types/app/runtime/signer-conditions.d.ts.map +1 -0
  97. package/types/app/transport/cesr-egress.d.ts +1 -0
  98. package/types/app/transport/cesr-egress.d.ts.map +1 -1
  99. package/types/core/errors.d.ts +20 -1
  100. package/types/core/errors.d.ts.map +1 -1
  101. package/types/core/json-schema-meta.d.ts +19 -0
  102. package/types/core/json-schema-meta.d.ts.map +1 -0
  103. package/types/core/json-schema.d.ts +29 -0
  104. package/types/core/json-schema.d.ts.map +1 -0
  105. package/types/core/scheming.d.ts +1 -0
  106. package/types/core/scheming.d.ts.map +1 -1
  107. package/types/db/replay/tel-replay.d.ts +1 -0
  108. package/types/db/replay/tel-replay.d.ts.map +1 -1
  109. package/types/db/store-lock.d.ts +2 -0
  110. package/types/db/store-lock.d.ts.map +1 -1
  111. package/types/runtime/index.d.ts +11 -9
  112. package/types/runtime/index.d.ts.map +1 -1
  113. package/types/runtime/web/output-barrier.d.ts +1 -0
  114. package/types/runtime/web/output-barrier.d.ts.map +1 -1
  115. package/types/version.d.ts +2 -2
@@ -30,7 +30,7 @@ import { Keeper, PreSit } from "../../db/keeping.js";
30
30
  import { makeNowIso8601 } from "../../time/mod.js";
31
31
  import { oobiReplyMessages } from "../bootstrap/oobi-replies.js";
32
32
  import { loadStoredReply } from "../delivery/stored-replies.js";
33
- import { acceptedGroupMembers, latestGroupContribution } from "../multisig/group-members.js";
33
+ import { acceptedGroupMembers, isAcceptedGroupMember, latestGroupContribution } from "../multisig/group-members.js";
34
34
  import { branToSaltQb64, encodeHugeNumber, ensureKeeperCryptoReady, Manager, normalizeSaltQb64, saltySigner, } from "./keeping.js";
35
35
  /** Reserved alias for the local signatory habitat record. */
36
36
  export const SIGNER = "__signatory__";
@@ -1547,6 +1547,10 @@ export class Habery {
1547
1547
  * - this is the TypeScript analogue of `Habery.joinGroupHab()`
1548
1548
  * - it does not fabricate key state; accepted KEL state must still arrive
1549
1549
  * through `Kevery.processEvent()`
1550
+ * - KERIpy overwrites the recorded member unconditionally. Here a recorded member that is still a
1551
+ * signing or rotation member of the group's accepted key state is kept (a Habery holding two valid
1552
+ * members must not switch which one signs), and one that is in neither list is replaced by `mhab`.
1553
+ * Replacing creates no event and signs nothing; membership itself changes only by rotation.
1550
1554
  */
1551
1555
  joinGroupHab(pre, group, mhab, smids, rmids, ns) {
1552
1556
  if (!pre) {
@@ -1569,18 +1573,54 @@ export class Habery {
1569
1573
  throw new ValidationError(`Group alias ${group} is already bound to ${existingName}.`);
1570
1574
  }
1571
1575
  const existingRecord = this.db.getHab(pre);
1572
- if (existingRecord?.mid && existingRecord.mid !== mhab.pre) {
1573
- throw new ValidationError(`Group prefix ${pre} is already joined as ${existingRecord.name}.`);
1576
+ const replacing = !!existingRecord?.mid && existingRecord.mid !== mhab.pre;
1577
+ if (replacing) {
1578
+ // Membership is decided by accepted key state, never by the caller's lists or a proposal.
1579
+ if (!this.db.getKever(pre)) {
1580
+ throw new ValidationError(`Group prefix ${pre} is already joined as ${existingRecord.name}; whether member ${existingRecord.mid} `
1581
+ + `is still a member cannot be judged until the group has accepted key state.`);
1582
+ }
1583
+ if (isAcceptedGroupMember(this.db, pre, existingRecord.mid)) {
1584
+ throw new ValidationError(`Group prefix ${pre} is already joined as ${existingRecord.name} by member ${existingRecord.mid}, `
1585
+ + `which is still a member in its accepted key state.`);
1586
+ }
1587
+ if (!isAcceptedGroupMember(this.db, pre, mhab.pre)) {
1588
+ throw new ValidationError(`Local member identifier ${mhab.pre} is not a member in the accepted key state of group ${pre}.`);
1589
+ }
1574
1590
  }
1575
1591
  const hab = this.habs.get(pre) ?? new Hab(group, this.db, this.ks, this.mgr, this.cf, this.rtr, this.rvy, this.kevery, ns, pre);
1576
- this.persistGroupHabRecord(pre, group, mhab.pre, signingMembers, rotationMembers, ns);
1592
+ this.persistGroupHabRecord(pre, group, mhab.pre, signingMembers, rotationMembers, ns, existingRecord ?? undefined);
1577
1593
  this.db.groups.add(pre);
1578
1594
  if (hab.accepted) {
1579
1595
  this.markAcceptedGroupHab(pre);
1580
1596
  }
1581
1597
  this.habs.set(pre, hab);
1598
+ if (replacing && !this.readonly) {
1599
+ // The stored lists must follow accepted state, which the old member's record kept from being committed.
1600
+ this.recoverAcceptedGroupMembers(pre);
1601
+ this.retireGroupMemberState(pre, existingRecord.mid);
1602
+ }
1582
1603
  return hab;
1583
1604
  }
1605
+ /**
1606
+ * Drop the durable rows that address `group` to its former local member `old`: registry root copies
1607
+ * escrowed for `old` (the holder check now refuses them, so a retry would only fail) and the markers
1608
+ * of copies sent to `old` as a recipient. Rows keyed by the group itself stay. Live operations are
1609
+ * owned by the runtime and are not reached from here.
1610
+ */
1611
+ retireGroupMemberState(group, old) {
1612
+ for (const [keys] of [...this.db.rrpe.getTopItemIter()]) {
1613
+ const serder = this.db.exns.get([keys.join(".")]);
1614
+ const data = serder?.sad?.a;
1615
+ if (serder?.route === "/registry/root" && serder.sad?.ri === old && data?.gid === group) {
1616
+ this.db.rrpe.rem(keys);
1617
+ }
1618
+ }
1619
+ for (const [keys] of [...this.db.rrsn.getTopItemIter([group])]) {
1620
+ if (keys[2] === old)
1621
+ this.db.rrsn.rem(keys);
1622
+ }
1623
+ }
1584
1624
  /** Commit accepted group member metadata after the corresponding group event is accepted. */
1585
1625
  commitGroupHabMembers(pre, smids, rmids = smids) {
1586
1626
  const record = this.db.getHab(pre);
@@ -1774,12 +1814,15 @@ export class Habery {
1774
1814
  return sigers;
1775
1815
  }
1776
1816
  /** Persist group habitat metadata and member ordinals. */
1777
- persistGroupHabRecord(pre, group, mid, smids, rmids, ns) {
1817
+ persistGroupHabRecord(pre, group, mid, smids, rmids, ns, replaced) {
1778
1818
  this.db.pinHab(pre, new HabitatRecord({
1779
1819
  hid: pre,
1780
1820
  name: group,
1781
1821
  domain: ns,
1782
1822
  mid,
1823
+ // A rejoin or replacement keeps the rest of the record.
1824
+ sid: replaced?.sid,
1825
+ watchers: replaced?.watchers ? [...replaced.watchers] : undefined,
1783
1826
  smids: [...smids],
1784
1827
  rmids: [...rmids],
1785
1828
  }));
@@ -1878,7 +1921,7 @@ export function branToSeedAeid(bran) {
1878
1921
  * durable DB state rather than depending on process-local creation history.
1879
1922
  */
1880
1923
  export function* openHabery(args) {
1881
- const { name, base = "", temp = false, headDirPath, compat = false, readonly = false, cf: providedCf, createConfig, skipConfig = false, skipSignator = false, opener, stores, bran, seed, aeid, salt, algo, } = args;
1924
+ const { name, base = "", temp = false, headDirPath, compat = false, readonly = false, cf: providedCf, createConfig, skipConfig = false, skipSignator = false, opener, stores, bran, seed, aeid, salt, algo, heldSigners, } = args;
1882
1925
  let db;
1883
1926
  let ks;
1884
1927
  let cf;
@@ -1921,6 +1964,9 @@ export function* openHabery(args) {
1921
1964
  algo,
1922
1965
  salt: normalizeSaltQb64(salt),
1923
1966
  });
1967
+ // Before the Habery exists, so before any runtime built on it can sign. A bad entry fails the open.
1968
+ if (heldSigners)
1969
+ mgr.hold(heldSigners);
1924
1970
  const hby = new Habery(name, base, temp, headDirPath, compat, readonly, db, ks, mgr, cf, skipSignator, opener, !!stores);
1925
1971
  delivered = true;
1926
1972
  return hby;
@@ -193,6 +193,25 @@ function resolveSuiteCodes(codes, count, code, { label, allowZero = false, }) {
193
193
  function emptyLot(dt = "") {
194
194
  return new PubLot({ pubs: [], ridx: 0, kidx: 0, dt });
195
195
  }
196
+ /**
197
+ * No private key for `pub`: neither held (`Manager.hold`) nor stored in the Keeper. A custodial host releases
198
+ * its signers on purpose, so a background loop that needs one treats this as a condition to wait out, not as a
199
+ * failure of the runtime. The message is the one the Keeper has always used.
200
+ */
201
+ export class SignerNotHeldError extends Error {
202
+ constructor(pub) {
203
+ super(`Missing prikey in db for pubkey=${pub}`);
204
+ /** The qualified public key whose private key is missing. */
205
+ Object.defineProperty(this, "pub", {
206
+ enumerable: true,
207
+ configurable: true,
208
+ writable: true,
209
+ value: void 0
210
+ });
211
+ this.name = "SignerNotHeldError";
212
+ this.pub = pub;
213
+ }
214
+ }
196
215
  /**
197
216
  * Key-management coordinator backed by `Keeper` state.
198
217
  *
@@ -251,6 +270,13 @@ export class Manager {
251
270
  writable: true,
252
271
  value: new Map()
253
272
  });
273
+ /** Told after `hold()` borrows signers; a loop that waited for a missing signer resumes on it. */
274
+ Object.defineProperty(this, "holdListeners", {
275
+ enumerable: true,
276
+ configurable: true,
277
+ writable: true,
278
+ value: new Set()
279
+ });
254
280
  const { ks, seed = "", aeid = "", pidx = 0, algo = Algos.salty, salt = randomSaltQb64(), tier = Tiers.low, } = args;
255
281
  this._ks = ks;
256
282
  this._seed = seed;
@@ -496,6 +522,17 @@ export class Manager {
496
522
  }
497
523
  for (const [pub, signer] of batch)
498
524
  this.held.set(pub, signer);
525
+ for (const listener of [...this.holdListeners])
526
+ listener();
527
+ }
528
+ /**
529
+ * Be told after each `hold()`, so a background loop whose signer was released can resume at once.
530
+ *
531
+ * @returns A function that stops the notifications.
532
+ */
533
+ onHold(listener) {
534
+ this.holdListeners.add(listener);
535
+ return () => this.holdListeners.delete(listener);
499
536
  }
500
537
  /**
501
538
  * Drop held signer references: all of them, or only those for `pubs`.
@@ -519,7 +556,7 @@ export class Manager {
519
556
  return held;
520
557
  const signer = this.ks.pris.get(pub, this.signerDecrypter());
521
558
  if (!signer) {
522
- throw new Error(`Missing prikey in db for pubkey=${pub}`);
559
+ throw new SignerNotHeldError(pub);
523
560
  }
524
561
  return signer;
525
562
  }
@@ -50,7 +50,7 @@ import { proposeRegistryEvent } from "../multisig/proposing.js";
50
50
  import { envelopeFromFrame } from "../protocol/parsering.js";
51
51
  import { exchangeWire } from "./ipex-discloser.js";
52
52
  import { acceptedRegistryEvidence, isIssuanceOrigin } from "./ipex-node-authentication.js";
53
- import { ISSUANCE_QUERY_RESEND_MS, } from "./issuance.js";
53
+ import { ISSUANCE_QUERY_RESEND_MS, partyComplete, } from "./issuance.js";
54
54
  /** Milliseconds between scans of the stored wrappers for the final proof. */
55
55
  const PROOF_SCAN_MS = 1000;
56
56
  /** How many of the group's latest accepted events {@link deriveGroupCustody} considers as coordinators. */
@@ -327,7 +327,7 @@ function* proposalOf(ops, grant, proposal) {
327
327
  */
328
328
  export function* approveGroupIssuance(ops, grantSaid, options = {}) {
329
329
  const { deps, discloser } = ops;
330
- if (ops.live.has(grantSaid) || deps.exc.complete(grantSaid)) {
330
+ if (ops.liveOf(grantSaid, "issuer") || partyComplete(deps, grantSaid, "issuer")) {
331
331
  throw new ValidationError("Issuance was already begun for this Grant");
332
332
  }
333
333
  const { child } = yield* proposalOf(ops, grantSaid, options.proposal);
@@ -413,7 +413,7 @@ function pendingGrant(deps, said) {
413
413
  */
414
414
  export function approveGroupIssuanceExchange(ops, said, coordinator) {
415
415
  const { deps } = ops;
416
- if (ops.live.has(said) || deps.exc.complete(said)) {
416
+ if (ops.liveOf(said, "holder") || partyComplete(deps, said, "holder")) {
417
417
  throw new ValidationError("Issuance was already begun for this Grant");
418
418
  }
419
419
  const { grant, nodes } = pendingGrant(deps, said);
@@ -430,7 +430,7 @@ export function approveGroupIssuanceExchange(ops, said, coordinator) {
430
430
  */
431
431
  export function* followGroupIssuanceProof(ops, said, options = {}) {
432
432
  const { deps } = ops;
433
- if (ops.live.has(said) || deps.exc.complete(said)) {
433
+ if (ops.liveOf(said, "issuer") || partyComplete(deps, said, "issuer")) {
434
434
  throw new ValidationError("Issuance was already begun for this Grant");
435
435
  }
436
436
  const { grant, nodes } = pendingGrant(deps, said);
@@ -647,7 +647,7 @@ export function registerGroupRoutes(ops) {
647
647
  acceptsNests: true,
648
648
  verify: (args) => structural(deps.hby, args.serder, args.nests ?? []),
649
649
  handle: (args) => {
650
- for (const op of ops.live.values()) {
650
+ for (const op of ops.liveOps()) {
651
651
  if (op.group?.accepts(args.serder, args.nests)) {
652
652
  op.group.pending.set(args.serder.said, { wrapper: args.serder, child: args.nests[0] });
653
653
  }
@@ -761,7 +761,10 @@ export function* stepGroup(ops, op) {
761
761
  for (const peer of party.others())
762
762
  yield* send(peer, msg);
763
763
  };
764
- 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"))
765
768
  return true;
766
769
  if (op.expires === null) {
767
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.");