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.
- package/esm/keri/npm/src/_dnt.polyfills.js +1 -127
- package/esm/keri/npm/src/acdc/custody.js +23 -0
- package/esm/keri/npm/src/acdc/dhking.js +6 -1
- package/esm/keri/npm/src/acdc/salting.js +18 -0
- package/esm/keri/npm/src/acdc/scheming.js +31 -18
- package/esm/keri/npm/src/acdc/unblinding.js +112 -6
- package/esm/keri/npm/src/app/delivery/forwarding.js +9 -0
- package/esm/keri/npm/src/app/delivery/respondant.js +21 -0
- package/esm/keri/npm/src/app/identity/habbing.js +4 -1
- package/esm/keri/npm/src/app/identity/keeping.js +38 -1
- package/esm/keri/npm/src/app/ipex/group-ipex.js +14 -0
- package/esm/keri/npm/src/app/ipex/group-issuance.js +15 -9
- package/esm/keri/npm/src/app/ipex/ipex-building.js +2 -1
- package/esm/keri/npm/src/app/ipex/ipex-discloser.js +32 -8
- package/esm/keri/npm/src/app/ipex/ipex-grouping.js +16 -8
- package/esm/keri/npm/src/app/ipex/ipex-node-authentication.js +42 -18
- package/esm/keri/npm/src/app/ipex/issuance.js +136 -40
- package/esm/keri/npm/src/app/ipex/pending-presentations.js +50 -0
- package/esm/keri/npm/src/app/ipex/presenting.js +44 -2
- package/esm/keri/npm/src/app/ipex/unblinding-workflow.js +141 -30
- package/esm/keri/npm/src/app/mailbox/mailbox-poller.js +37 -9
- package/esm/keri/npm/src/app/mailbox/mailboxing.js +9 -3
- package/esm/keri/npm/src/app/multisig/counseling.js +83 -4
- package/esm/keri/npm/src/app/multisig/event-forwarding.js +27 -0
- package/esm/keri/npm/src/app/multisig/grouping.js +120 -1
- package/esm/keri/npm/src/app/multisig/multisig-messaging.js +33 -6
- package/esm/keri/npm/src/app/multisig/multisig-workflows.js +5 -9
- package/esm/keri/npm/src/app/multisig/proposals.js +1 -1
- package/esm/keri/npm/src/app/runtime/agent-runtime.js +43 -23
- package/esm/keri/npm/src/app/runtime/ipex-auxiliaries.js +59 -25
- package/esm/keri/npm/src/app/runtime/operation-owners.js +1 -0
- package/esm/keri/npm/src/app/runtime/signer-conditions.js +51 -0
- package/esm/keri/npm/src/app/transport/cesr-egress.js +6 -0
- package/esm/keri/npm/src/cli/credentials/ipex.js +7 -1
- package/esm/keri/npm/src/cli/messaging/group-join.js +7 -3
- package/esm/keri/npm/src/cli/messaging/multisig.js +37 -11
- package/esm/keri/npm/src/core/errors.js +91 -3
- package/esm/keri/npm/src/core/json-schema-meta.js +688 -0
- package/esm/keri/npm/src/core/json-schema.js +147 -0
- package/esm/keri/npm/src/core/kel/eventing.js +20 -2
- package/esm/keri/npm/src/core/scheming.js +27 -26
- package/esm/keri/npm/src/db/replay/tel-replay.js +6 -0
- package/esm/keri/npm/src/db/store-lock.js +12 -0
- package/esm/keri/npm/src/runtime/index.js +9 -8
- package/esm/keri/npm/src/runtime/web/output-barrier.js +9 -0
- package/esm/keri/npm/src/version.js +2 -2
- package/package.json +2 -2
- package/types/_dnt.polyfills.d.ts +0 -99
- package/types/_dnt.polyfills.d.ts.map +1 -1
- package/types/acdc/custody.d.ts +7 -0
- package/types/acdc/custody.d.ts.map +1 -1
- package/types/acdc/dhking.d.ts.map +1 -1
- package/types/acdc/salting.d.ts +2 -0
- package/types/acdc/salting.d.ts.map +1 -1
- package/types/acdc/scheming.d.ts +2 -0
- package/types/acdc/scheming.d.ts.map +1 -1
- package/types/acdc/unblinding.d.ts +16 -5
- package/types/acdc/unblinding.d.ts.map +1 -1
- package/types/app/delivery/forwarding.d.ts +1 -0
- package/types/app/delivery/forwarding.d.ts.map +1 -1
- package/types/app/delivery/respondant.d.ts +7 -0
- package/types/app/delivery/respondant.d.ts.map +1 -1
- package/types/app/identity/habbing.d.ts +10 -1
- package/types/app/identity/habbing.d.ts.map +1 -1
- package/types/app/identity/keeping.d.ts +18 -0
- package/types/app/identity/keeping.d.ts.map +1 -1
- package/types/app/ipex/group-ipex.d.ts +7 -0
- package/types/app/ipex/group-ipex.d.ts.map +1 -1
- package/types/app/ipex/group-issuance.d.ts +1 -1
- package/types/app/ipex/group-issuance.d.ts.map +1 -1
- package/types/app/ipex/ipex-building.d.ts.map +1 -1
- package/types/app/ipex/ipex-discloser.d.ts +31 -19
- package/types/app/ipex/ipex-discloser.d.ts.map +1 -1
- package/types/app/ipex/ipex-grouping.d.ts.map +1 -1
- package/types/app/ipex/ipex-node-authentication.d.ts +9 -1
- package/types/app/ipex/ipex-node-authentication.d.ts.map +1 -1
- package/types/app/ipex/issuance.d.ts +41 -8
- package/types/app/ipex/issuance.d.ts.map +1 -1
- package/types/app/ipex/pending-presentations.d.ts +62 -1
- package/types/app/ipex/pending-presentations.d.ts.map +1 -1
- package/types/app/ipex/presenting.d.ts +41 -4
- package/types/app/ipex/presenting.d.ts.map +1 -1
- package/types/app/ipex/unblinding-workflow.d.ts +46 -10
- package/types/app/ipex/unblinding-workflow.d.ts.map +1 -1
- package/types/app/mailbox/mailbox-poller.d.ts +11 -2
- package/types/app/mailbox/mailbox-poller.d.ts.map +1 -1
- package/types/app/mailbox/mailboxing.d.ts +4 -1
- package/types/app/mailbox/mailboxing.d.ts.map +1 -1
- package/types/app/multisig/counseling.d.ts +22 -1
- package/types/app/multisig/counseling.d.ts.map +1 -1
- package/types/app/multisig/event-forwarding.d.ts +6 -0
- package/types/app/multisig/event-forwarding.d.ts.map +1 -0
- package/types/app/multisig/grouping.d.ts +26 -0
- package/types/app/multisig/grouping.d.ts.map +1 -1
- package/types/app/multisig/multisig-messaging.d.ts +23 -6
- package/types/app/multisig/multisig-messaging.d.ts.map +1 -1
- package/types/app/multisig/multisig-workflows.d.ts.map +1 -1
- package/types/app/multisig/proposals.d.ts +3 -2
- package/types/app/multisig/proposals.d.ts.map +1 -1
- package/types/app/multisig/proposing.d.ts +8 -1
- package/types/app/multisig/proposing.d.ts.map +1 -1
- package/types/app/runtime/agent-runtime.d.ts +3 -0
- package/types/app/runtime/agent-runtime.d.ts.map +1 -1
- package/types/app/runtime/ipex-auxiliaries.d.ts +8 -3
- package/types/app/runtime/ipex-auxiliaries.d.ts.map +1 -1
- package/types/app/runtime/operation-owners.d.ts.map +1 -1
- package/types/app/runtime/signer-conditions.d.ts +32 -0
- package/types/app/runtime/signer-conditions.d.ts.map +1 -0
- package/types/app/transport/cesr-egress.d.ts +1 -0
- package/types/app/transport/cesr-egress.d.ts.map +1 -1
- package/types/cli/credentials/ipex.d.ts.map +1 -1
- package/types/cli/messaging/group-join.d.ts +4 -1
- package/types/cli/messaging/group-join.d.ts.map +1 -1
- package/types/cli/messaging/multisig.d.ts +1 -0
- package/types/cli/messaging/multisig.d.ts.map +1 -1
- package/types/core/errors.d.ts +20 -1
- package/types/core/errors.d.ts.map +1 -1
- package/types/core/json-schema-meta.d.ts +19 -0
- package/types/core/json-schema-meta.d.ts.map +1 -0
- package/types/core/json-schema.d.ts +29 -0
- package/types/core/json-schema.d.ts.map +1 -0
- package/types/core/scheming.d.ts +1 -0
- package/types/core/scheming.d.ts.map +1 -1
- package/types/db/replay/tel-replay.d.ts +1 -0
- package/types/db/replay/tel-replay.d.ts.map +1 -1
- package/types/db/store-lock.d.ts +2 -0
- package/types/db/store-lock.d.ts.map +1 -1
- package/types/runtime/index.d.ts +12 -11
- package/types/runtime/index.d.ts.map +1 -1
- package/types/runtime/web/output-barrier.d.ts +1 -0
- package/types/runtime/web/output-barrier.d.ts.map +1 -1
- 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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
65
|
-
* from the stored signatures or source seal and the stored nests.
|
|
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 =
|
|
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),
|
|
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)
|
|
467
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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
|
}
|