keri-ts 2.1.0 → 2.2.1
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/README.md +30 -2
- package/esm/keri/npm/src/acdc/authoring.js +195 -0
- package/esm/keri/npm/src/acdc/custody.js +187 -0
- package/esm/keri/npm/src/acdc/dhking.js +244 -32
- package/esm/keri/npm/src/acdc/regbasing.js +10 -0
- package/esm/keri/npm/src/acdc/salting.js +16 -1
- package/esm/keri/npm/src/acdc/scheming.js +93 -54
- package/esm/keri/npm/src/acdc/unblinding.js +118 -95
- package/esm/keri/npm/src/app/bootstrap/authenticating.js +4 -6
- package/esm/keri/npm/src/app/bootstrap/oobi-resolving.js +41 -0
- package/esm/keri/npm/src/app/bootstrap/oobiery.js +4 -6
- package/esm/keri/npm/src/app/credentials/credential-revoking.js +1 -0
- package/esm/keri/npm/src/app/credentials/credential-workflows.js +2 -0
- package/esm/keri/npm/src/app/credentials/registry-settling.js +44 -0
- package/esm/keri/npm/src/app/credentials/root-replicating.js +68 -60
- package/esm/keri/npm/src/app/credentials/root-replicator.js +198 -0
- package/esm/keri/npm/src/app/credentials/tel-completion.js +36 -0
- package/esm/keri/npm/src/app/delivery/forwarding.js +16 -3
- package/esm/keri/npm/src/app/exchanges/exchanging.js +14 -4
- package/esm/keri/npm/src/app/identity/challenging.js +50 -0
- package/esm/keri/npm/src/app/identity/delegating.js +77 -77
- package/esm/keri/npm/src/app/identity/delegation-approval.js +333 -0
- package/esm/keri/npm/src/app/identity/event-completion.js +154 -0
- package/esm/keri/npm/src/app/identity/habbing.js +129 -6
- package/esm/keri/npm/src/app/identity/keeping.js +77 -4
- package/esm/keri/npm/src/app/ipex/group-ipex.js +341 -0
- package/esm/keri/npm/src/app/ipex/group-issuance.js +905 -0
- package/esm/keri/npm/src/app/ipex/ipex-admitting.js +120 -0
- package/esm/keri/npm/src/app/ipex/ipex-conversation.js +197 -0
- package/esm/keri/npm/src/app/ipex/ipex-discloser.js +591 -0
- package/esm/keri/npm/src/app/ipex/ipex-granting.js +93 -0
- package/esm/keri/npm/src/app/ipex/ipex-grouping.js +50 -19
- package/esm/keri/npm/src/app/ipex/ipex-node-authentication.js +42 -18
- package/esm/keri/npm/src/app/ipex/issuance.js +896 -0
- package/esm/keri/npm/src/app/ipex/pending-presentations.js +46 -0
- package/esm/keri/npm/src/app/ipex/presentation-files.js +200 -0
- package/esm/keri/npm/src/app/ipex/presenting.js +408 -0
- package/esm/keri/npm/src/app/ipex/receive-issuance.js +86 -0
- package/esm/keri/npm/src/app/ipex/unblinding-workflow.js +667 -0
- package/esm/keri/npm/src/app/mailbox/ipex-arrival.js +103 -0
- package/esm/keri/npm/src/app/mailbox/kel-publisher.js +322 -0
- package/esm/keri/npm/src/app/mailbox/kel-publishing.js +65 -0
- package/esm/keri/npm/src/app/mailbox/kel-submission.js +24 -0
- package/esm/keri/npm/src/app/mailbox/mailbox-admin.js +208 -0
- package/esm/keri/npm/src/app/mailbox/mailbox-poller.js +212 -47
- package/esm/keri/npm/src/app/mailbox/mailboxing.js +10 -5
- package/esm/keri/npm/src/app/multisig/counseling.js +488 -0
- package/esm/keri/npm/src/app/multisig/group-members.js +18 -0
- package/esm/keri/npm/src/app/multisig/multisig-messaging.js +33 -6
- package/esm/keri/npm/src/app/multisig/multisig-workflows.js +98 -147
- package/esm/keri/npm/src/app/multisig/proposals.js +281 -0
- package/esm/keri/npm/src/app/multisig/proposing.js +122 -0
- package/esm/keri/npm/src/app/notifications/signaling.js +5 -5
- package/esm/keri/npm/src/app/protocol/parsering.js +26 -1
- package/esm/keri/npm/src/app/protocol/protocol-processing.js +9 -3
- package/esm/keri/npm/src/app/queries/query-workflows.js +98 -0
- package/esm/keri/npm/src/app/runtime/agent-runtime.js +106 -55
- package/esm/keri/npm/src/app/runtime/cue-runtime.js +3 -6
- package/esm/keri/npm/src/app/runtime/delivery-outbox.js +87 -0
- package/esm/keri/npm/src/app/runtime/doing.js +17 -0
- package/esm/keri/npm/src/app/runtime/ipex-auxiliaries.js +207 -0
- package/esm/keri/npm/src/app/runtime/operation-owners.js +151 -0
- package/esm/keri/npm/src/app/runtime/retry-backoff.js +93 -0
- package/esm/keri/npm/src/app/runtime/workflow-contract.js +80 -0
- package/esm/keri/npm/src/app/transport/http-client.js +25 -6
- package/esm/keri/npm/src/app/witness/http-endpoints.js +4 -4
- package/esm/keri/npm/src/cli/common/event-completion.js +49 -0
- package/esm/keri/npm/src/cli/common/runtime-workers.js +23 -0
- package/esm/keri/npm/src/cli/credentials/ipex.js +164 -191
- package/esm/keri/npm/src/cli/credentials/vc.js +53 -72
- package/esm/keri/npm/src/cli/endpoints/did.js +12 -3
- package/esm/keri/npm/src/cli/endpoints/oobi.js +10 -13
- package/esm/keri/npm/src/cli/identity/challenge.js +8 -34
- package/esm/keri/npm/src/cli/identity/delegate.js +81 -258
- package/esm/keri/npm/src/cli/identity/incept.js +15 -32
- package/esm/keri/npm/src/cli/identity/interact.js +5 -17
- package/esm/keri/npm/src/cli/identity/rotate.js +8 -36
- package/esm/keri/npm/src/cli/index.js +3 -1
- package/esm/keri/npm/src/cli/messaging/group-join.js +83 -0
- package/esm/keri/npm/src/cli/messaging/group-wait.js +88 -0
- package/esm/keri/npm/src/cli/messaging/multisig.js +190 -285
- package/esm/keri/npm/src/cli/messaging/root-copy-lines.js +31 -0
- package/esm/keri/npm/src/core/kel/eventing.js +66 -10
- package/esm/keri/npm/src/core/mailbox/kram-policy.js +39 -6
- package/esm/keri/npm/src/core/mailbox/kraming.js +2 -0
- package/esm/keri/npm/src/core/records.js +23 -0
- package/esm/keri/npm/src/core/routing.js +13 -0
- package/esm/keri/npm/src/db/basing.js +34 -2
- package/esm/keri/npm/src/db/core/indexeddb-persistence.js +221 -0
- package/esm/keri/npm/src/db/indexeddb.js +98 -0
- package/esm/keri/npm/src/db/keeping.js +11 -2
- package/esm/keri/npm/src/db/store-lock.js +150 -0
- package/esm/keri/npm/src/npm/db-indexeddb.browser.js +2 -0
- package/esm/keri/npm/src/npm/db-indexeddb.js +3 -0
- package/esm/keri/npm/src/npm/db.browser.js +2 -0
- package/esm/keri/npm/src/npm/index.browser.js +9 -0
- package/esm/keri/npm/src/npm/runtime-web.browser.js +2 -0
- package/esm/keri/npm/src/npm/runtime-web.js +3 -0
- package/esm/keri/npm/src/npm/runtime.browser.js +2 -0
- package/esm/keri/npm/src/runtime/index.js +51 -4
- package/esm/keri/npm/src/runtime/web/output-barrier.js +47 -0
- package/esm/keri/npm/src/runtime/web/persistent-storage.js +37 -0
- package/esm/keri/npm/src/runtime/web.js +10 -0
- package/esm/keri/npm/src/vdr/tel-publication.js +16 -0
- package/esm/keri/npm/src/version.js +2 -2
- package/package.json +15 -2
- package/types/acdc/authoring.d.ts +54 -0
- package/types/acdc/authoring.d.ts.map +1 -0
- package/types/acdc/custody.d.ts +84 -0
- package/types/acdc/custody.d.ts.map +1 -0
- package/types/acdc/dhking.d.ts +94 -3
- package/types/acdc/dhking.d.ts.map +1 -1
- package/types/acdc/regbasing.d.ts +4 -0
- package/types/acdc/regbasing.d.ts.map +1 -1
- package/types/acdc/salting.d.ts +6 -1
- 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 +58 -19
- package/types/acdc/unblinding.d.ts.map +1 -1
- package/types/app/bootstrap/authenticating.d.ts +3 -1
- package/types/app/bootstrap/authenticating.d.ts.map +1 -1
- package/types/app/bootstrap/oobi-resolving.d.ts +92 -0
- package/types/app/bootstrap/oobi-resolving.d.ts.map +1 -0
- package/types/app/bootstrap/oobiery.d.ts +3 -1
- package/types/app/bootstrap/oobiery.d.ts.map +1 -1
- package/types/app/credentials/credential-revoking.d.ts +3 -0
- package/types/app/credentials/credential-revoking.d.ts.map +1 -1
- package/types/app/credentials/credential-workflows.d.ts +5 -0
- package/types/app/credentials/credential-workflows.d.ts.map +1 -1
- package/types/app/credentials/registry-settling.d.ts +85 -0
- package/types/app/credentials/registry-settling.d.ts.map +1 -0
- package/types/app/credentials/root-replicating.d.ts +56 -44
- package/types/app/credentials/root-replicating.d.ts.map +1 -1
- package/types/app/credentials/root-replicator.d.ts +105 -0
- package/types/app/credentials/root-replicator.d.ts.map +1 -0
- package/types/app/credentials/tel-completion.d.ts +55 -0
- package/types/app/credentials/tel-completion.d.ts.map +1 -0
- package/types/app/delivery/forwarding.d.ts +6 -0
- package/types/app/delivery/forwarding.d.ts.map +1 -1
- package/types/app/exchanges/exchanging.d.ts.map +1 -1
- package/types/app/identity/challenging.d.ts +54 -0
- package/types/app/identity/challenging.d.ts.map +1 -1
- package/types/app/identity/delegating.d.ts +21 -4
- package/types/app/identity/delegating.d.ts.map +1 -1
- package/types/app/identity/delegation-approval.d.ts +228 -0
- package/types/app/identity/delegation-approval.d.ts.map +1 -0
- package/types/app/identity/event-completion.d.ts +151 -0
- package/types/app/identity/event-completion.d.ts.map +1 -0
- package/types/app/identity/habbing.d.ts +67 -2
- package/types/app/identity/habbing.d.ts.map +1 -1
- package/types/app/identity/keeping.d.ts +49 -3
- package/types/app/identity/keeping.d.ts.map +1 -1
- package/types/app/ipex/group-ipex.d.ts +254 -0
- package/types/app/ipex/group-ipex.d.ts.map +1 -0
- package/types/app/ipex/group-issuance.d.ts +281 -0
- package/types/app/ipex/group-issuance.d.ts.map +1 -0
- package/types/app/ipex/ipex-admitting.d.ts +47 -0
- package/types/app/ipex/ipex-admitting.d.ts.map +1 -0
- package/types/app/ipex/ipex-conversation.d.ts +195 -0
- package/types/app/ipex/ipex-conversation.d.ts.map +1 -0
- package/types/app/ipex/ipex-discloser.d.ts +156 -0
- package/types/app/ipex/ipex-discloser.d.ts.map +1 -0
- package/types/app/ipex/ipex-granting.d.ts +145 -0
- package/types/app/ipex/ipex-granting.d.ts.map +1 -0
- package/types/app/ipex/ipex-grouping.d.ts +18 -0
- 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 +445 -0
- package/types/app/ipex/issuance.d.ts.map +1 -0
- package/types/app/ipex/pending-presentations.d.ts +25 -0
- package/types/app/ipex/pending-presentations.d.ts.map +1 -0
- package/types/app/ipex/presentation-files.d.ts +47 -0
- package/types/app/ipex/presentation-files.d.ts.map +1 -0
- package/types/app/ipex/presenting.d.ts +300 -0
- package/types/app/ipex/presenting.d.ts.map +1 -0
- package/types/app/ipex/receive-issuance.d.ts +99 -0
- package/types/app/ipex/receive-issuance.d.ts.map +1 -0
- package/types/app/ipex/unblinding-workflow.d.ts +257 -0
- package/types/app/ipex/unblinding-workflow.d.ts.map +1 -0
- package/types/app/mailbox/ipex-arrival.d.ts +104 -0
- package/types/app/mailbox/ipex-arrival.d.ts.map +1 -0
- package/types/app/mailbox/kel-publisher.d.ts +141 -0
- package/types/app/mailbox/kel-publisher.d.ts.map +1 -0
- package/types/app/mailbox/kel-publishing.d.ts +70 -0
- package/types/app/mailbox/kel-publishing.d.ts.map +1 -0
- package/types/app/mailbox/kel-submission.d.ts +18 -0
- package/types/app/mailbox/kel-submission.d.ts.map +1 -0
- package/types/app/mailbox/mailbox-admin.d.ts +113 -0
- package/types/app/mailbox/mailbox-admin.d.ts.map +1 -0
- package/types/app/mailbox/mailbox-poller.d.ts +92 -3
- package/types/app/mailbox/mailbox-poller.d.ts.map +1 -1
- package/types/app/mailbox/mailboxing.d.ts +3 -0
- package/types/app/mailbox/mailboxing.d.ts.map +1 -1
- package/types/app/multisig/counseling.d.ts +196 -0
- package/types/app/multisig/counseling.d.ts.map +1 -0
- package/types/app/multisig/group-members.d.ts +11 -0
- package/types/app/multisig/group-members.d.ts.map +1 -1
- package/types/app/multisig/grouping.d.ts +39 -20
- 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 +91 -22
- package/types/app/multisig/multisig-workflows.d.ts.map +1 -1
- package/types/app/multisig/proposals.d.ts +129 -0
- package/types/app/multisig/proposals.d.ts.map +1 -0
- package/types/app/multisig/proposing.d.ts +125 -0
- package/types/app/multisig/proposing.d.ts.map +1 -0
- package/types/app/notifications/signaling.d.ts +3 -1
- package/types/app/notifications/signaling.d.ts.map +1 -1
- package/types/app/protocol/parsering.d.ts +13 -0
- package/types/app/protocol/parsering.d.ts.map +1 -1
- package/types/app/protocol/protocol-processing.d.ts +7 -1
- package/types/app/protocol/protocol-processing.d.ts.map +1 -1
- package/types/app/queries/query-workflows.d.ts +98 -0
- package/types/app/queries/query-workflows.d.ts.map +1 -0
- package/types/app/runtime/agent-runtime.d.ts +72 -25
- package/types/app/runtime/agent-runtime.d.ts.map +1 -1
- package/types/app/runtime/cue-runtime.d.ts +2 -1
- package/types/app/runtime/cue-runtime.d.ts.map +1 -1
- package/types/app/runtime/delivery-outbox.d.ts +68 -0
- package/types/app/runtime/delivery-outbox.d.ts.map +1 -0
- package/types/app/runtime/doing.d.ts +27 -0
- package/types/app/runtime/doing.d.ts.map +1 -0
- package/types/app/runtime/ipex-auxiliaries.d.ts +74 -0
- package/types/app/runtime/ipex-auxiliaries.d.ts.map +1 -0
- package/types/app/runtime/operation-owners.d.ts +137 -0
- package/types/app/runtime/operation-owners.d.ts.map +1 -0
- package/types/app/runtime/retry-backoff.d.ts +49 -0
- package/types/app/runtime/retry-backoff.d.ts.map +1 -0
- package/types/app/runtime/workflow-contract.d.ts +79 -0
- package/types/app/runtime/workflow-contract.d.ts.map +1 -0
- package/types/app/transport/http-client.d.ts.map +1 -1
- package/types/cli/common/event-completion.d.ts +26 -0
- package/types/cli/common/event-completion.d.ts.map +1 -0
- package/types/cli/common/runtime-workers.d.ts +23 -0
- package/types/cli/common/runtime-workers.d.ts.map +1 -0
- package/types/cli/credentials/ipex.d.ts.map +1 -1
- package/types/cli/credentials/vc.d.ts +1 -1
- package/types/cli/credentials/vc.d.ts.map +1 -1
- package/types/cli/endpoints/did.d.ts.map +1 -1
- package/types/cli/endpoints/oobi.d.ts +3 -1
- package/types/cli/endpoints/oobi.d.ts.map +1 -1
- package/types/cli/identity/challenge.d.ts +1 -1
- package/types/cli/identity/challenge.d.ts.map +1 -1
- package/types/cli/identity/delegate.d.ts +14 -1
- package/types/cli/identity/delegate.d.ts.map +1 -1
- package/types/cli/identity/incept.d.ts.map +1 -1
- package/types/cli/identity/interact.d.ts.map +1 -1
- package/types/cli/identity/rotate.d.ts.map +1 -1
- package/types/cli/index.d.ts +3 -1
- package/types/cli/index.d.ts.map +1 -1
- package/types/cli/messaging/group-join.d.ts +56 -0
- package/types/cli/messaging/group-join.d.ts.map +1 -0
- package/types/cli/messaging/group-wait.d.ts +34 -0
- package/types/cli/messaging/group-wait.d.ts.map +1 -0
- package/types/cli/messaging/multisig.d.ts +29 -5
- package/types/cli/messaging/multisig.d.ts.map +1 -1
- package/types/cli/messaging/root-copy-lines.d.ts +39 -0
- package/types/cli/messaging/root-copy-lines.d.ts.map +1 -0
- package/types/core/cues.d.ts +11 -0
- package/types/core/cues.d.ts.map +1 -1
- package/types/core/kel/eventing.d.ts.map +1 -1
- package/types/core/mailbox/kram-policy.d.ts +14 -0
- package/types/core/mailbox/kram-policy.d.ts.map +1 -1
- package/types/core/mailbox/kraming.d.ts +1 -1
- package/types/core/mailbox/kraming.d.ts.map +1 -1
- package/types/core/records.d.ts +61 -0
- package/types/core/records.d.ts.map +1 -1
- package/types/core/routing.d.ts +6 -0
- package/types/core/routing.d.ts.map +1 -1
- package/types/db/basing.d.ts +8 -2
- package/types/db/basing.d.ts.map +1 -1
- package/types/db/core/indexeddb-persistence.d.ts +59 -0
- package/types/db/core/indexeddb-persistence.d.ts.map +1 -0
- package/types/db/indexeddb.d.ts +37 -0
- package/types/db/indexeddb.d.ts.map +1 -0
- package/types/db/keeping.d.ts +5 -2
- package/types/db/keeping.d.ts.map +1 -1
- package/types/db/store-lock.d.ts +72 -0
- package/types/db/store-lock.d.ts.map +1 -0
- package/types/npm/db-indexeddb.d.ts +4 -0
- package/types/npm/db-indexeddb.d.ts.map +1 -0
- package/types/npm/runtime-web.d.ts +4 -0
- package/types/npm/runtime-web.d.ts.map +1 -0
- package/types/runtime/index.d.ts +42 -7
- package/types/runtime/index.d.ts.map +1 -1
- package/types/runtime/web/output-barrier.d.ts +30 -0
- package/types/runtime/web/output-barrier.d.ts.map +1 -0
- package/types/runtime/web/persistent-storage.d.ts +36 -0
- package/types/runtime/web/persistent-storage.d.ts.map +1 -0
- package/types/runtime/web.d.ts +11 -0
- package/types/runtime/web.d.ts.map +1 -0
- package/types/vdr/tel-publication.d.ts +6 -0
- package/types/vdr/tel-publication.d.ts.map +1 -1
- package/types/version.d.ts +2 -2
- package/esm/keri/npm/src/cli/common/tel-completion.js +0 -24
- package/types/cli/common/tel-completion.d.ts +0 -8
- package/types/cli/common/tel-completion.d.ts.map +0 -1
|
@@ -0,0 +1,905 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Group DH issuance steps for the runtime's `IssuanceOperations` owner.
|
|
3
|
+
*
|
|
4
|
+
* A local group issues (or receives) a v2 DH credential through the same live table, status read and wait as an
|
|
5
|
+
* individual. This module holds what is specific to a group: the explicit consent calls, the per-operation
|
|
6
|
+
* state machine that `IssuanceOperations.processOnce` advances, and the child handler that lets the
|
|
7
|
+
* `/multisig/qry` and `/multisig/rpy` wrappers of DH reach it.
|
|
8
|
+
*
|
|
9
|
+
* Consent (the only calls that sign, each returning after local commit without waiting on a member):
|
|
10
|
+
*
|
|
11
|
+
* | who | call | what it signs |
|
|
12
|
+
* | ----------------------------- | ----------------------------------------- | --------------------------------------------------- |
|
|
13
|
+
* | issuer custodian, prepare | {@link prepareGroupIssuance} | the custodian's contribution to a fresh Grant |
|
|
14
|
+
* | issuer custodian, one consent | {@link proposeGroupIssuance} | prepare, then approve its own proposal |
|
|
15
|
+
* | issuer member | {@link approveGroupIssuance} | this member's Grant contribution, and consent to its later DH query contribution |
|
|
16
|
+
* | holder custodian or member | {@link approveGroupIssuanceExchange} | consent to DH (contributions follow as they arrive) |
|
|
17
|
+
* | a member that only follows | {@link followGroupIssuanceProof} | nothing; verifies the custodian's final proof |
|
|
18
|
+
*
|
|
19
|
+
* The registry update is a separate consent: the custodian proposes the BUP through `proposeRegistryEvent`
|
|
20
|
+
* (its own contribution is its approval) and every other issuer member approves that proposal with
|
|
21
|
+
* `approveGroupProposal`. Admit is a separate consent as well.
|
|
22
|
+
*
|
|
23
|
+
* Automatic, inside `processOnce`: sending a member's own proposal, authenticating the Grant, approving DH once
|
|
24
|
+
* the Grant is authenticated, the custodian's evidence, query and reply traffic, wrapper intake, finishing the
|
|
25
|
+
* Grant and the proof broadcast to the other members. Root copies are not sent here: the DH completion cue
|
|
26
|
+
* feeds the runtime's `RootReplicator`, which sends exactly one copy per member.
|
|
27
|
+
*
|
|
28
|
+
* Reload: nothing about a group operation is durable but the protocol stores. A member that reloads reads
|
|
29
|
+
* `awaitingExchangeConsent` for a pending Grant it holds no root for and consents again with the same call;
|
|
30
|
+
* its signatures are deterministic, so consenting twice produces the same bytes. A custodian that reloaded
|
|
31
|
+
* after retaining its root resumes when the stores allow it ({@link deriveGroupCustody}).
|
|
32
|
+
*/
|
|
33
|
+
import { bytesEqual, collectPayload, concatBytes, Counter, framePayload, parseBytes, SerderACDC, SerderKERI, Vrsn_2_0, } from "cesr-ts";
|
|
34
|
+
import { call } from "effection";
|
|
35
|
+
import { RegistryAuthor } from "../../acdc/authoring.js";
|
|
36
|
+
import { RegistryCustody } from "../../acdc/custody.js";
|
|
37
|
+
import { Dhke, grantTransactionId } from "../../acdc/dhking.js";
|
|
38
|
+
import { vet } from "../../acdc/regeventing.js";
|
|
39
|
+
import { RegistryRoot } from "../../acdc/salting.js";
|
|
40
|
+
import { ValidationError } from "../../core/errors.js";
|
|
41
|
+
import { Kever } from "../../core/kel/kever.js";
|
|
42
|
+
import { messagize } from "../../core/protocol-serialization.js";
|
|
43
|
+
import { loadNests } from "../../db/exchange/exchange-nests.js";
|
|
44
|
+
import { timestampMicros } from "../../time/mod.js";
|
|
45
|
+
import { registryEventRef, registryEventStatus, settleRegistryEvent, } from "../credentials/registry-settling.js";
|
|
46
|
+
import { groupElectedMember, groupSigningMembers, isLocalGroupHab, latestGroupContribution, localGroupMember, } from "../multisig/group-members.js";
|
|
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";
|
|
49
|
+
import { proposeRegistryEvent } from "../multisig/proposing.js";
|
|
50
|
+
import { envelopeFromFrame } from "../protocol/parsering.js";
|
|
51
|
+
import { exchangeWire } from "./ipex-discloser.js";
|
|
52
|
+
import { acceptedRegistryEvidence, isIssuanceOrigin } from "./ipex-node-authentication.js";
|
|
53
|
+
import { ISSUANCE_QUERY_RESEND_MS, } from "./issuance.js";
|
|
54
|
+
/** Milliseconds between scans of the stored wrappers for the final proof. */
|
|
55
|
+
const PROOF_SCAN_MS = 1000;
|
|
56
|
+
/** How many of the group's latest accepted events {@link deriveGroupCustody} considers as coordinators. */
|
|
57
|
+
const GROUP_COORDINATOR_SEARCH = 16;
|
|
58
|
+
const ALLOWED_CHILD_FACTORS = ["TransIdxSigGroups", "TransLastIdxSigGroups", "ControllerIdxSigs"];
|
|
59
|
+
/**
|
|
60
|
+
* The local member of a group as one party to a group issuance: the group, the member that signs for it, and the
|
|
61
|
+
* custodian its accepted coordinator elects. Resolving checks everything a consent relies on once, so no later step
|
|
62
|
+
* re-derives it. Not exported from the runtime barrel.
|
|
63
|
+
*/
|
|
64
|
+
export class GroupParty {
|
|
65
|
+
constructor(hby, group, member, coordinator, custodian) {
|
|
66
|
+
Object.defineProperty(this, "hby", {
|
|
67
|
+
enumerable: true,
|
|
68
|
+
configurable: true,
|
|
69
|
+
writable: true,
|
|
70
|
+
value: hby
|
|
71
|
+
});
|
|
72
|
+
Object.defineProperty(this, "group", {
|
|
73
|
+
enumerable: true,
|
|
74
|
+
configurable: true,
|
|
75
|
+
writable: true,
|
|
76
|
+
value: group
|
|
77
|
+
});
|
|
78
|
+
Object.defineProperty(this, "member", {
|
|
79
|
+
enumerable: true,
|
|
80
|
+
configurable: true,
|
|
81
|
+
writable: true,
|
|
82
|
+
value: member
|
|
83
|
+
});
|
|
84
|
+
Object.defineProperty(this, "coordinator", {
|
|
85
|
+
enumerable: true,
|
|
86
|
+
configurable: true,
|
|
87
|
+
writable: true,
|
|
88
|
+
value: coordinator
|
|
89
|
+
});
|
|
90
|
+
Object.defineProperty(this, "custodian", {
|
|
91
|
+
enumerable: true,
|
|
92
|
+
configurable: true,
|
|
93
|
+
writable: true,
|
|
94
|
+
value: custodian
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The party of `group` under `coordinator` (default the group's latest establishment event).
|
|
99
|
+
*
|
|
100
|
+
* @throws ValidationError for an owner composed without group collaborators, an identifier that is not a local
|
|
101
|
+
* group, or a coordinator that is not an accepted event electing a current signing member.
|
|
102
|
+
*/
|
|
103
|
+
static resolve(deps, group, coordinator) {
|
|
104
|
+
if (!deps.group)
|
|
105
|
+
throw new ValidationError("This issuance owner was composed without group collaborators");
|
|
106
|
+
const { hby } = deps;
|
|
107
|
+
if (!isLocalGroupHab(hby, group))
|
|
108
|
+
throw new ValidationError("Group issuance requires a local group identifier");
|
|
109
|
+
const member = localGroupMember(hby, group.pre);
|
|
110
|
+
const said = coordinator ?? group.kever.lastEst.d;
|
|
111
|
+
const event = group.db.getEvtSerder(group.pre, said);
|
|
112
|
+
const accepted = event ? { sn: event.sn, said: event.said } : null;
|
|
113
|
+
const custodian = accepted ? groupElectedMember(hby, group.pre, accepted) : null;
|
|
114
|
+
if (!accepted || !custodian || !latestGroupContribution(group.db, member.pre, group.kever.verfers)) {
|
|
115
|
+
throw new ValidationError("The coordinator is not an accepted group event electing a current signing member");
|
|
116
|
+
}
|
|
117
|
+
return new GroupParty(hby, group, member, accepted, custodian);
|
|
118
|
+
}
|
|
119
|
+
/** Whether the local member is the elected custodian. */
|
|
120
|
+
get lead() {
|
|
121
|
+
return this.member.pre === this.custodian;
|
|
122
|
+
}
|
|
123
|
+
/** The other current signing members of the group that are not held in this Habery; local ones share its stores. */
|
|
124
|
+
others() {
|
|
125
|
+
return remoteGroupMembers(this.hby, this.member, groupSigningMembers(this.hby, this.group.pre));
|
|
126
|
+
}
|
|
127
|
+
/** The election this party stands for. */
|
|
128
|
+
election() {
|
|
129
|
+
return { coordinator: this.coordinator, custodian: this.custodian, lead: this.lead };
|
|
130
|
+
}
|
|
131
|
+
/** Wrap `child` for the other members; the parent authenticates one exact child and never supplies its threshold. */
|
|
132
|
+
*wrap(child, route) {
|
|
133
|
+
const body = multisigExnBody(this.member.pre, { route, attrs: { gid: this.group.pre } }, child.body, {
|
|
134
|
+
version: Vrsn_2_0,
|
|
135
|
+
});
|
|
136
|
+
const frame = parseBytes(this.member.endorse(body, { gvrsn: Vrsn_2_0 }))[0];
|
|
137
|
+
frame.nests = [child];
|
|
138
|
+
return yield* wire(frame);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** The local group `pre` names; a Grant's sender or recipient must be one for a group issuance. */
|
|
142
|
+
function localGroup(deps, pre) {
|
|
143
|
+
const group = typeof pre === "string" ? deps.hby.habs.get(pre) : undefined;
|
|
144
|
+
if (!group || !isLocalGroupHab(deps.hby, group)) {
|
|
145
|
+
throw new ValidationError("The Grant is not sent or received by a local group identifier");
|
|
146
|
+
}
|
|
147
|
+
return group;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Pure read of a group's election under `coordinator` (default its latest establishment event): who is the
|
|
151
|
+
* custodian and whether the local member is it.
|
|
152
|
+
*
|
|
153
|
+
* @throws ValidationError as {@link GroupParty.resolve}.
|
|
154
|
+
*/
|
|
155
|
+
export function groupIssuanceElection(ops, group, coordinator) {
|
|
156
|
+
return GroupParty.resolve(ops.deps, localGroup(ops.deps, group), coordinator).election();
|
|
157
|
+
}
|
|
158
|
+
function* wire(frame) {
|
|
159
|
+
return yield* call(() => collectPayload(framePayload(frame)));
|
|
160
|
+
}
|
|
161
|
+
/** Concatenate frames as one nested node stream, the input of proof support. */
|
|
162
|
+
function* nodeStream(nodes) {
|
|
163
|
+
return concatBytes(Counter.makeGVC(Vrsn_2_0), ...yield* call(() => Promise.all(nodes.map((node) => collectPayload(framePayload(node))))));
|
|
164
|
+
}
|
|
165
|
+
/** Replay accepted wrapper or completed Grant bytes through the exchanger's exact evidence serializer. */
|
|
166
|
+
function* storedBytes(exc, said) {
|
|
167
|
+
const chunks = [];
|
|
168
|
+
yield* call(async () => {
|
|
169
|
+
for await (const chunk of exc.replay(said))
|
|
170
|
+
chunks.push(chunk);
|
|
171
|
+
});
|
|
172
|
+
return concatBytes(...chunks);
|
|
173
|
+
}
|
|
174
|
+
/** Local consent starts the original Exchanger deadline even while the group signature remains partial. */
|
|
175
|
+
function* retainLocalGrant(exc, child) {
|
|
176
|
+
const proof = envelopeFromFrame(child);
|
|
177
|
+
yield* exc.processEvent({
|
|
178
|
+
serder: child.body,
|
|
179
|
+
tsgs: proof.tsgs,
|
|
180
|
+
lsgs: proof.ssgs,
|
|
181
|
+
cigars: proof.cigars,
|
|
182
|
+
nests: child.nests,
|
|
183
|
+
gvrsn: Vrsn_2_0,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* The party a restarted group custodian was frozen with, derived from the stores. The local member is the
|
|
188
|
+
* custodian when it holds the root of every registry the Grant discloses and received none of them as a copy: a
|
|
189
|
+
* root reaches a non-custodian only as a stored `/registry/root` exchange addressed to it, so that exchange tells
|
|
190
|
+
* a member that was sent its copy from the member whose own DH exchange retained the root. The coordinator is the
|
|
191
|
+
* latest accepted group event whose threshold evidence elects that member; DH only checks that the frozen
|
|
192
|
+
* coordinator still elects the frozen custodian, so any such event serves. Null when the member received a copy
|
|
193
|
+
* of a disclosed root, holds none, or no accepted event elects it.
|
|
194
|
+
*/
|
|
195
|
+
export function deriveGroupCustody(deps, hab, grant) {
|
|
196
|
+
const { hby } = deps;
|
|
197
|
+
if (!isLocalGroupHab(hby, hab) || !hab.kever)
|
|
198
|
+
return null;
|
|
199
|
+
const member = localGroupMember(hby, hab.pre);
|
|
200
|
+
const registries = new Set();
|
|
201
|
+
for (const nest of deps.exc.retainedInput(grant)?.nests ?? []) {
|
|
202
|
+
const rd = nest.body.ked?.rd;
|
|
203
|
+
if (typeof rd === "string")
|
|
204
|
+
registries.add(rd);
|
|
205
|
+
}
|
|
206
|
+
if (!registries.size || [...registries].some((registry) => !hab.mgr.hasRegistryRoot(hab.pre, registry))) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
if (receivedRootCopy(hby, hab.pre, member.pre, registries))
|
|
210
|
+
return null;
|
|
211
|
+
const events = [...hby.db.getKelItemIter(hab.pre)].slice(-GROUP_COORDINATOR_SEARCH);
|
|
212
|
+
for (const [, said] of events.reverse()) {
|
|
213
|
+
const event = hby.db.getEvtSerder(hab.pre, said);
|
|
214
|
+
if (!event || event.sn === null)
|
|
215
|
+
continue;
|
|
216
|
+
const coordinator = { sn: event.sn, said: event.said };
|
|
217
|
+
if (groupElectedMember(hby, hab.pre, coordinator) === member.pre) {
|
|
218
|
+
return { coordinator, custodian: member.pre, lead: true };
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
/** Whether `member` was sent a `/registry/root` copy of the group's root for any of `registries`. */
|
|
224
|
+
function receivedRootCopy(hby, group, member, registries) {
|
|
225
|
+
for (const [, copy] of hby.db.exns.getTopItemIter()) {
|
|
226
|
+
if (copy.route !== RegistryRoot.Route || copy.pre === member || copy.sad?.ri !== member)
|
|
227
|
+
continue;
|
|
228
|
+
const data = copy.sad?.a;
|
|
229
|
+
if (data?.gid === group && typeof data.rd === "string" && registries.has(data.rd))
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Prepare the custodian's first partial Grant for a fresh credential, preserving the discloser's fresh-origin and
|
|
236
|
+
* far-node checks, and return the proposal the other issuer members consent to. Signs the custodian's Grant
|
|
237
|
+
* contribution and files it locally; sends nothing and approves no DH exchange. The group is the credential's
|
|
238
|
+
* managed registry's.
|
|
239
|
+
*
|
|
240
|
+
* @throws ValidationError unless the local member is the elected custodian of the group that owns the
|
|
241
|
+
* credential's registry.
|
|
242
|
+
*/
|
|
243
|
+
export function* prepareGroupIssuance(ops, draft, coordinator) {
|
|
244
|
+
const { deps, discloser } = ops, { credential, options } = draft;
|
|
245
|
+
const group = credential.regid ? deps.rgy.regs.get(credential.regid)?.hab : undefined;
|
|
246
|
+
if (!group)
|
|
247
|
+
throw new ValidationError("Fresh issuance requires the issuing group's managed local registry");
|
|
248
|
+
const party = GroupParty.resolve(deps, group, coordinator);
|
|
249
|
+
if (!party.lead)
|
|
250
|
+
throw new ValidationError("Only the elected issuer custodian prepares its managed fresh issuance");
|
|
251
|
+
const prepared = yield* discloser.prepareIssuanceGrantBody(group.pre, credential, options);
|
|
252
|
+
const child = parseBytes(exchangeWire(group, prepared.exn, prepared.nests))[0];
|
|
253
|
+
const proposal = yield* party.wrap(child, MULTISIG_EXN_ROUTE);
|
|
254
|
+
yield* retainLocalGrant(deps.exc, child);
|
|
255
|
+
yield* deps.reactor.processBytes(concatBytes(discloser.proofSupport(prepared.raw, group), proposal), { local: true });
|
|
256
|
+
return { grant: prepared.exn.said, proposal };
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Stored Grant proposals, one per Grant, of `group` or of every local group. The wrapper kept is another member's
|
|
260
|
+
* where there is one (the proposal as received), and this member's own contribution otherwise.
|
|
261
|
+
*/
|
|
262
|
+
export function groupIssuanceProposals(ops, group) {
|
|
263
|
+
const { hby, exc } = ops.deps, found = new Map();
|
|
264
|
+
for (const [, parent] of hby.db.exns.getTopItemIter()) {
|
|
265
|
+
if (parent.route !== MULTISIG_EXN_ROUTE || parent.pvrsn.major !== 2)
|
|
266
|
+
continue;
|
|
267
|
+
const child = loadNests(hby.db, parent.said)[0]?.body;
|
|
268
|
+
if (!(child instanceof SerderKERI) || child.route !== "/ipex/grant" || typeof child.sad?.ri !== "string"
|
|
269
|
+
|| (group
|
|
270
|
+
? child.pre !== group.pre
|
|
271
|
+
: !hby.habs.get(child.pre) || !isLocalGroupHab(hby, hby.habs.get(child.pre))))
|
|
272
|
+
continue;
|
|
273
|
+
if (!isIssuanceOrigin(child.pre, loadNests(hby.db, parent.said)[0]?.nests?.[0]))
|
|
274
|
+
continue;
|
|
275
|
+
const self = localGroupMember(hby, child.pre).pre, known = found.get(child.said);
|
|
276
|
+
if (known && (parent.pre === self || known.proposer !== self))
|
|
277
|
+
continue;
|
|
278
|
+
found.set(child.said, {
|
|
279
|
+
grant: child.said,
|
|
280
|
+
wrapper: parent.said,
|
|
281
|
+
group: child.pre,
|
|
282
|
+
proposer: parent.pre,
|
|
283
|
+
recipient: child.sad.ri,
|
|
284
|
+
complete: exc.complete(child.said),
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
return [...found.values()];
|
|
288
|
+
}
|
|
289
|
+
/** The exact proposal wrapper and child frame for `grant`: the supplied bytes, or the stored wrapper. */
|
|
290
|
+
function* proposalOf(ops, grant, proposal) {
|
|
291
|
+
const { deps } = ops;
|
|
292
|
+
let parent, child;
|
|
293
|
+
if (proposal) {
|
|
294
|
+
const packets = parseBytes(proposal);
|
|
295
|
+
parent = packets[0]?.body;
|
|
296
|
+
child = packets[0]?.nests?.[0];
|
|
297
|
+
if (packets.length !== 1 || packets[0].nests?.length !== 1) {
|
|
298
|
+
throw new ValidationError("Select one exact group issuance Grant proposal");
|
|
299
|
+
}
|
|
300
|
+
yield* deps.reactor.processBytes(proposal);
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
const stored = groupIssuanceProposals(ops).find((item) => item.grant === grant);
|
|
304
|
+
parent = stored ? deps.hby.db.exns.get([stored.wrapper]) ?? undefined : undefined;
|
|
305
|
+
child = parent ? loadNests(deps.hby.db, parent.said)[0] : undefined;
|
|
306
|
+
}
|
|
307
|
+
if (!(parent instanceof SerderKERI) || parent.route !== MULTISIG_EXN_ROUTE || !(child?.body instanceof SerderKERI)
|
|
308
|
+
|| child.body.said !== grant)
|
|
309
|
+
throw new ValidationError("Select one exact group issuance Grant proposal");
|
|
310
|
+
if (!deps.exc.verifyAccepted(parent.said, MULTISIG_EXN_ROUTE)) {
|
|
311
|
+
throw new ValidationError("Grant proposal lacks member authentication");
|
|
312
|
+
}
|
|
313
|
+
return { parent, child };
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* The issuer member's consent to a group Grant, and through it to contributing this member's signature to the DH
|
|
317
|
+
* query when the custodian's arrives. The group is the proposal's. Verifies the proposal (it must carry the
|
|
318
|
+
* elected custodian's signature) and rebuilds the exact Grant from its fresh inputs, signs this member's
|
|
319
|
+
* contribution, files it and records the operation. Returns after local commit: sending the contribution to the
|
|
320
|
+
* other members, and everything after, is `processOnce`.
|
|
321
|
+
*
|
|
322
|
+
* Calling it again after a reload produces the same signature and resumes the operation: a member that reloads
|
|
323
|
+
* between consent and completion consents again.
|
|
324
|
+
*
|
|
325
|
+
* @throws ValidationError for a proposal the stores do not prove, one the elected custodian did not sign, an
|
|
326
|
+
* operation already begun, or a Grant that differs from its exact fresh issuance inputs.
|
|
327
|
+
*/
|
|
328
|
+
export function* approveGroupIssuance(ops, grantSaid, options = {}) {
|
|
329
|
+
const { deps, discloser } = ops;
|
|
330
|
+
if (ops.live.has(grantSaid) || deps.exc.complete(grantSaid)) {
|
|
331
|
+
throw new ValidationError("Issuance was already begun for this Grant");
|
|
332
|
+
}
|
|
333
|
+
const { child } = yield* proposalOf(ops, grantSaid, options.proposal);
|
|
334
|
+
const party = GroupParty.resolve(deps, localGroup(deps, child.body.pre), options.coordinator);
|
|
335
|
+
const { group } = party;
|
|
336
|
+
const factors = envelopeFromFrame(child);
|
|
337
|
+
if (!factors.tsgs.length
|
|
338
|
+
|| factors.attachmentGroups.some((attachment) => attachment.name.replace(/^Big/, "") !== "TransIdxSigGroups")) {
|
|
339
|
+
throw new ValidationError("Fresh group Grant requires only explicit establishment-bound group contributions");
|
|
340
|
+
}
|
|
341
|
+
const signed = new Set();
|
|
342
|
+
for (const proof of factors.tsgs) {
|
|
343
|
+
const accepted = proof.sn <= BigInt(Number.MAX_SAFE_INTEGER)
|
|
344
|
+
? group.db.kels.getLast(proof.pre, Number(proof.sn))
|
|
345
|
+
: null;
|
|
346
|
+
const establishment = accepted ? group.db.getEvtSerder(proof.pre, accepted) : null;
|
|
347
|
+
const verified = establishment?.estive
|
|
348
|
+
? Kever.verifyIndexedSignatures(child.body.raw, proof.sigers, establishment.verfers)
|
|
349
|
+
: null;
|
|
350
|
+
if (proof.pre !== group.pre || accepted !== proof.said || !verified || !proof.sigers.length) {
|
|
351
|
+
throw new ValidationError("Invalid supplied group Grant contribution");
|
|
352
|
+
}
|
|
353
|
+
if (verified.sigers.length !== proof.sigers.length) {
|
|
354
|
+
throw new ValidationError("Invalid supplied group Grant contribution");
|
|
355
|
+
}
|
|
356
|
+
for (const index of verified.indices)
|
|
357
|
+
signed.add(index);
|
|
358
|
+
}
|
|
359
|
+
const custodian = latestGroupContribution(group.db, party.custodian, group.kever.verfers);
|
|
360
|
+
if (!custodian || !signed.has(custodian.index)) {
|
|
361
|
+
throw new ValidationError("The Grant proposal lacks the elected custodian's signature");
|
|
362
|
+
}
|
|
363
|
+
const grant = child.body, nodes = child.nests ?? [];
|
|
364
|
+
const origin = nodes[0]?.body;
|
|
365
|
+
if (!(origin instanceof SerderACDC))
|
|
366
|
+
throw new ValidationError("Group Grant lacks its exact origin");
|
|
367
|
+
const rebuilt = yield* discloser.prepareIssuanceGrantBody(group.pre, origin, {
|
|
368
|
+
recipient: grant.sad.ri,
|
|
369
|
+
message: grant.sad.a.m,
|
|
370
|
+
priorSaid: grant.sad.p || undefined,
|
|
371
|
+
date: grant.stamp,
|
|
372
|
+
kind: grant.kind,
|
|
373
|
+
plan: grant.sad.q.dp[0],
|
|
374
|
+
attrs: Object.fromEntries(Object.entries(grant.sad.a).filter(([key]) => !["m", "o", "ax"].includes(key))),
|
|
375
|
+
anchor: grant.sad.a.ax?.[0],
|
|
376
|
+
artifacts: yield* call(() => Promise.all(nodes.slice(1).map((node) => collectPayload(framePayload(node))))),
|
|
377
|
+
});
|
|
378
|
+
const exact = grant.ilk === "xip"
|
|
379
|
+
? new SerderKERI({ sad: { ...rebuilt.exn.sad, u: grant.sad.u }, kind: grant.kind, makify: true })
|
|
380
|
+
: rebuilt.exn;
|
|
381
|
+
if (!bytesEqual(exact.raw, grant.raw)) {
|
|
382
|
+
throw new ValidationError("Selected Grant differs from exact fresh issuance inputs");
|
|
383
|
+
}
|
|
384
|
+
const own = parseBytes(exchangeWire(group, grant, rebuilt.nests))[0];
|
|
385
|
+
own.attachments = [...child.attachments, ...own.attachments];
|
|
386
|
+
yield* retainLocalGrant(deps.exc, own);
|
|
387
|
+
const ownProposal = yield* party.wrap(own, MULTISIG_EXN_ROUTE);
|
|
388
|
+
yield* deps.reactor.processBytes(ownProposal, { local: true });
|
|
389
|
+
return ops.beginGroup(new GroupIssuance(party, "issuer", grant, nodes, { proposal: ownProposal, anchor: options.anchor }));
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* A custodian's one consent to issue a fresh credential: prepares the Grant ({@link prepareGroupIssuance}) and
|
|
393
|
+
* approves its own proposal ({@link approveGroupIssuance}). The other members approve the proposal it sent.
|
|
394
|
+
*/
|
|
395
|
+
export function* proposeGroupIssuance(ops, draft, options = {}) {
|
|
396
|
+
const prepared = yield* prepareGroupIssuance(ops, draft, options.coordinator);
|
|
397
|
+
return yield* approveGroupIssuance(ops, prepared.grant, { ...options, proposal: prepared.proposal });
|
|
398
|
+
}
|
|
399
|
+
function pendingGrant(deps, said) {
|
|
400
|
+
const retained = deps.exc.retainedInput(said, { pending: true });
|
|
401
|
+
const origin = retained?.nests?.[0]?.body;
|
|
402
|
+
if (!retained || !(origin instanceof SerderACDC) || !origin.regid) {
|
|
403
|
+
throw new ValidationError("The Grant is not an authenticated pending group issuance");
|
|
404
|
+
}
|
|
405
|
+
return { grant: retained.serder, nodes: retained.nests ?? [], origin };
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* The holder group member's consent to DH for one authenticated pending Grant addressed to the group (the group is
|
|
409
|
+
* the Grant's recipient). Approves DH through the coordinator's election; the member's contribution to the reply,
|
|
410
|
+
* and for the custodian the keys, follow from `processOnce`. Admit is a separate consent.
|
|
411
|
+
*
|
|
412
|
+
* @throws ValidationError for a Grant that is not pending for a local group, or an operation already begun.
|
|
413
|
+
*/
|
|
414
|
+
export function approveGroupIssuanceExchange(ops, said, coordinator) {
|
|
415
|
+
const { deps } = ops;
|
|
416
|
+
if (ops.live.has(said) || deps.exc.complete(said)) {
|
|
417
|
+
throw new ValidationError("Issuance was already begun for this Grant");
|
|
418
|
+
}
|
|
419
|
+
const { grant, nodes } = pendingGrant(deps, said);
|
|
420
|
+
const party = GroupParty.resolve(deps, localGroup(deps, grant.sad.ri), coordinator);
|
|
421
|
+
return ops.beginGroup(new GroupIssuance(party, "holder", grant, nodes));
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Follow a group issuance that has already been contributed to, only to verify and accept the custodian's final
|
|
425
|
+
* proof: the file-free path of an issuer member that left after contributing and returns for the proof. The group
|
|
426
|
+
* is the Grant's sender. Signs nothing. The proof may already be stored (it arrives by mailbox), or be supplied as
|
|
427
|
+
* bytes.
|
|
428
|
+
*
|
|
429
|
+
* @throws ValidationError for a Grant this runtime's groups did not send, or one already begun.
|
|
430
|
+
*/
|
|
431
|
+
export function* followGroupIssuanceProof(ops, said, options = {}) {
|
|
432
|
+
const { deps } = ops;
|
|
433
|
+
if (ops.live.has(said) || deps.exc.complete(said)) {
|
|
434
|
+
throw new ValidationError("Issuance was already begun for this Grant");
|
|
435
|
+
}
|
|
436
|
+
const { grant, nodes } = pendingGrant(deps, said);
|
|
437
|
+
const party = GroupParty.resolve(deps, localGroup(deps, grant.pre), options.coordinator);
|
|
438
|
+
if (options.proof)
|
|
439
|
+
yield* deps.reactor.processBytes(options.proof);
|
|
440
|
+
return ops.beginGroup(new GroupIssuance(party, "issuer", grant, nodes, { mode: "proofOnly" }));
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* The custodian's exact public proof of a completed group issuance: the proof support and the
|
|
444
|
+
* `/multisig/ipex-proof` wrapper every other member received, read back from the stores. A copy for members that
|
|
445
|
+
* were away and for a file hand-off.
|
|
446
|
+
*
|
|
447
|
+
* @throws ValidationError unless the local member is the custodian of the Grant's sending group and has published
|
|
448
|
+
* the proof of an accepted Grant.
|
|
449
|
+
*/
|
|
450
|
+
export function* groupIssuanceProof(ops, said) {
|
|
451
|
+
const { deps, discloser } = ops, retained = deps.exc.retainedInput(said);
|
|
452
|
+
if (!retained?.nests?.length || !deps.exc.complete(said)) {
|
|
453
|
+
throw new ValidationError("Only the custodian of an accepted group Grant has its public proof");
|
|
454
|
+
}
|
|
455
|
+
const group = localGroup(deps, retained.serder.pre), member = localGroupMember(deps.hby, group.pre);
|
|
456
|
+
for (const [, parent] of deps.hby.db.exns.getTopItemIter()) {
|
|
457
|
+
const attrs = parent.sad?.a;
|
|
458
|
+
if (parent.route === MULTISIG_IPEX_PROOF_ROUTE && parent.pre === member.pre && attrs?.gid === group.pre
|
|
459
|
+
&& attrs?.grant === said) {
|
|
460
|
+
return concatBytes(discloser.proofSupport(yield* nodeStream(retained.nests), group), yield* storedBytes(deps.exc, parent.said));
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
throw new ValidationError("The custodian has not published the proof of this group Grant");
|
|
464
|
+
}
|
|
465
|
+
/** The per-operation state `IssuanceOperations` keeps for a group party; memory only. */
|
|
466
|
+
export class GroupIssuance {
|
|
467
|
+
constructor(party, role, grant, nodes, start = {}) {
|
|
468
|
+
Object.defineProperty(this, "party", {
|
|
469
|
+
enumerable: true,
|
|
470
|
+
configurable: true,
|
|
471
|
+
writable: true,
|
|
472
|
+
value: party
|
|
473
|
+
});
|
|
474
|
+
Object.defineProperty(this, "role", {
|
|
475
|
+
enumerable: true,
|
|
476
|
+
configurable: true,
|
|
477
|
+
writable: true,
|
|
478
|
+
value: role
|
|
479
|
+
});
|
|
480
|
+
Object.defineProperty(this, "grant", {
|
|
481
|
+
enumerable: true,
|
|
482
|
+
configurable: true,
|
|
483
|
+
writable: true,
|
|
484
|
+
value: grant
|
|
485
|
+
});
|
|
486
|
+
Object.defineProperty(this, "nodes", {
|
|
487
|
+
enumerable: true,
|
|
488
|
+
configurable: true,
|
|
489
|
+
writable: true,
|
|
490
|
+
value: nodes
|
|
491
|
+
});
|
|
492
|
+
/** Wrappers received and not yet taken into DH, by SAID. */
|
|
493
|
+
Object.defineProperty(this, "pending", {
|
|
494
|
+
enumerable: true,
|
|
495
|
+
configurable: true,
|
|
496
|
+
writable: true,
|
|
497
|
+
value: new Map()
|
|
498
|
+
});
|
|
499
|
+
Object.defineProperty(this, "seen", {
|
|
500
|
+
enumerable: true,
|
|
501
|
+
configurable: true,
|
|
502
|
+
writable: true,
|
|
503
|
+
value: new Set()
|
|
504
|
+
});
|
|
505
|
+
Object.defineProperty(this, "approved", {
|
|
506
|
+
enumerable: true,
|
|
507
|
+
configurable: true,
|
|
508
|
+
writable: true,
|
|
509
|
+
value: void 0
|
|
510
|
+
});
|
|
511
|
+
Object.defineProperty(this, "contributed", {
|
|
512
|
+
enumerable: true,
|
|
513
|
+
configurable: true,
|
|
514
|
+
writable: true,
|
|
515
|
+
value: false
|
|
516
|
+
});
|
|
517
|
+
Object.defineProperty(this, "started", {
|
|
518
|
+
enumerable: true,
|
|
519
|
+
configurable: true,
|
|
520
|
+
writable: true,
|
|
521
|
+
value: false
|
|
522
|
+
});
|
|
523
|
+
/** Exact DH message this member contributes to, once frozen. */
|
|
524
|
+
Object.defineProperty(this, "frozen", {
|
|
525
|
+
enumerable: true,
|
|
526
|
+
configurable: true,
|
|
527
|
+
writable: true,
|
|
528
|
+
value: void 0
|
|
529
|
+
});
|
|
530
|
+
Object.defineProperty(this, "query", {
|
|
531
|
+
enumerable: true,
|
|
532
|
+
configurable: true,
|
|
533
|
+
writable: true,
|
|
534
|
+
value: void 0
|
|
535
|
+
});
|
|
536
|
+
Object.defineProperty(this, "nextQuery", {
|
|
537
|
+
enumerable: true,
|
|
538
|
+
configurable: true,
|
|
539
|
+
writable: true,
|
|
540
|
+
value: 0
|
|
541
|
+
});
|
|
542
|
+
Object.defineProperty(this, "nextProofScan", {
|
|
543
|
+
enumerable: true,
|
|
544
|
+
configurable: true,
|
|
545
|
+
writable: true,
|
|
546
|
+
value: 0
|
|
547
|
+
});
|
|
548
|
+
/** The custodian's registry update, once proposed. */
|
|
549
|
+
Object.defineProperty(this, "registryUpdate", {
|
|
550
|
+
enumerable: true,
|
|
551
|
+
configurable: true,
|
|
552
|
+
writable: true,
|
|
553
|
+
value: void 0
|
|
554
|
+
});
|
|
555
|
+
/** Proposal wrapper this member still owes the other members. */
|
|
556
|
+
Object.defineProperty(this, "proposal", {
|
|
557
|
+
enumerable: true,
|
|
558
|
+
configurable: true,
|
|
559
|
+
writable: true,
|
|
560
|
+
value: void 0
|
|
561
|
+
});
|
|
562
|
+
Object.defineProperty(this, "nodeBytes", {
|
|
563
|
+
enumerable: true,
|
|
564
|
+
configurable: true,
|
|
565
|
+
writable: true,
|
|
566
|
+
value: void 0
|
|
567
|
+
});
|
|
568
|
+
Object.defineProperty(this, "mode", {
|
|
569
|
+
enumerable: true,
|
|
570
|
+
configurable: true,
|
|
571
|
+
writable: true,
|
|
572
|
+
value: void 0
|
|
573
|
+
});
|
|
574
|
+
Object.defineProperty(this, "anchor", {
|
|
575
|
+
enumerable: true,
|
|
576
|
+
configurable: true,
|
|
577
|
+
writable: true,
|
|
578
|
+
value: void 0
|
|
579
|
+
});
|
|
580
|
+
this.mode = start.mode ?? "consent";
|
|
581
|
+
this.proposal = start.proposal;
|
|
582
|
+
this.anchor = start.anchor;
|
|
583
|
+
this.approved = this.mode === "resumed";
|
|
584
|
+
}
|
|
585
|
+
/** SAID of the Grant. */
|
|
586
|
+
get said() {
|
|
587
|
+
return this.grant.said;
|
|
588
|
+
}
|
|
589
|
+
/** DH transaction id: the opening Grant's SAID, or the transaction a continuation Grant joins. */
|
|
590
|
+
get xid() {
|
|
591
|
+
return grantTransactionId(this.grant);
|
|
592
|
+
}
|
|
593
|
+
/** The other party: the holder (issuer role) or the issuer (holder role). */
|
|
594
|
+
get peer() {
|
|
595
|
+
return this.role === "issuer" ? this.grant.sad.ri : this.grant.pre;
|
|
596
|
+
}
|
|
597
|
+
/** The disclosed credential the Grant carries first. */
|
|
598
|
+
get origin() {
|
|
599
|
+
return this.nodes[0].body;
|
|
600
|
+
}
|
|
601
|
+
/** The credential's registry. */
|
|
602
|
+
get registry() {
|
|
603
|
+
return this.origin.regid;
|
|
604
|
+
}
|
|
605
|
+
/** Whether `parent` is a DH child wrapper of this operation, beyond the structure every group wrapper has. */
|
|
606
|
+
accepts(parent, children) {
|
|
607
|
+
const issuer = this.role === "issuer";
|
|
608
|
+
if (parent.route !== (issuer ? MULTISIG_QRY_ROUTE : MULTISIG_RPY_ROUTE))
|
|
609
|
+
return false;
|
|
610
|
+
const body = children[0].body, data = body.sad[issuer ? "q" : "a"];
|
|
611
|
+
return body.pre === this.party.group.pre && data?.x === this.xid
|
|
612
|
+
&& (!this.frozen || bytesEqual(this.frozen, body.raw));
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* The structure every DH child wrapper has, whichever operation it is for: one ordinary-signature query or
|
|
617
|
+
* reply of a local group's DH transaction, wrapped by a current signing member of that group. Operation-specific
|
|
618
|
+
* checks follow in {@link GroupIssuance.accepts}.
|
|
619
|
+
*/
|
|
620
|
+
function structural(hby, parent, children) {
|
|
621
|
+
const child = children[0], body = child?.body, route = parent.route;
|
|
622
|
+
if (children.length !== 1 || !(body instanceof SerderKERI) || parent.pvrsn.major !== 2 || parent.pvrsn.minor !== 0
|
|
623
|
+
|| (route !== MULTISIG_QRY_ROUTE && route !== MULTISIG_RPY_ROUTE))
|
|
624
|
+
return false;
|
|
625
|
+
const field = route === MULTISIG_QRY_ROUTE ? "qry" : "rpy", gid = body.pre;
|
|
626
|
+
const group = gid ? hby.habs.get(gid) : undefined;
|
|
627
|
+
if (!gid || !group || !isLocalGroupHab(hby, group) || !groupSigningMembers(hby, gid).includes(parent.pre)
|
|
628
|
+
|| JSON.stringify(parent.sad.a) !== JSON.stringify({ gid, [field]: body.said }))
|
|
629
|
+
return false;
|
|
630
|
+
const data = body.sad[field === "qry" ? "q" : "a"];
|
|
631
|
+
return body.pvrsn.major === 2 && body.pvrsn.minor === 0 && body.ilk === field && body.route === Dhke.Route
|
|
632
|
+
&& typeof data?.x === "string" && (field !== "qry" || body.sad.rr === Dhke.Route) && !child.nests?.length
|
|
633
|
+
&& envelopeFromFrame(child).attachmentGroups.every((group) => ALLOWED_CHILD_FACTORS.includes(group.name.replace(/^Big/, "")));
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Delegate the DH child wrappers of `/multisig/qry` and `/multisig/rpy` to this owner, once. The delegation is
|
|
637
|
+
* structural: a wrapper that arrives before a member consents is kept as evidence and found by the operation
|
|
638
|
+
* when it starts, and consent alone ever makes a member contribute. Registration refuses a second owner on
|
|
639
|
+
* one runtime, which is the multiplexor's rule.
|
|
640
|
+
*/
|
|
641
|
+
export function registerGroupRoutes(ops) {
|
|
642
|
+
if (ops.groupRoutes)
|
|
643
|
+
return;
|
|
644
|
+
const { mux } = ops.deps.group, deps = ops.deps;
|
|
645
|
+
ops.groupRoutes = [MULTISIG_QRY_ROUTE, MULTISIG_RPY_ROUTE].map((route) => [route, {
|
|
646
|
+
resource: route,
|
|
647
|
+
acceptsNests: true,
|
|
648
|
+
verify: (args) => structural(deps.hby, args.serder, args.nests ?? []),
|
|
649
|
+
handle: (args) => {
|
|
650
|
+
for (const op of ops.live.values()) {
|
|
651
|
+
if (op.group?.accepts(args.serder, args.nests)) {
|
|
652
|
+
op.group.pending.set(args.serder.said, { wrapper: args.serder, child: args.nests[0] });
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
},
|
|
656
|
+
}]);
|
|
657
|
+
for (const [route, handler] of ops.groupRoutes)
|
|
658
|
+
mux.addChildHandler(route, Dhke.Route, handler);
|
|
659
|
+
}
|
|
660
|
+
/** Release the delegation; a reopened runtime builds a new multiplexor, so this is for owners that outlive a phase. */
|
|
661
|
+
export function releaseGroupRoutes(ops) {
|
|
662
|
+
for (const [route, handler] of ops.groupRoutes ?? []) {
|
|
663
|
+
ops.deps.group?.mux.removeChildHandler(route, Dhke.Route, handler);
|
|
664
|
+
}
|
|
665
|
+
ops.groupRoutes = null;
|
|
666
|
+
}
|
|
667
|
+
/** Pick up the DH wrappers the exchanger stored before this operation started. */
|
|
668
|
+
function recover(deps, g) {
|
|
669
|
+
for (const [, parent] of deps.hby.db.exns.getTopItemIter()) {
|
|
670
|
+
if (parent.route !== MULTISIG_QRY_ROUTE && parent.route !== MULTISIG_RPY_ROUTE)
|
|
671
|
+
continue;
|
|
672
|
+
const input = deps.exc.retainedInput(parent.said);
|
|
673
|
+
if (input?.nests && structural(deps.hby, parent, input.nests) && g.accepts(parent, input.nests)) {
|
|
674
|
+
g.pending.set(parent.said, { wrapper: parent, child: input.nests[0] });
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Verify a custodian's exact final proof against the original issuer factors and accept the Grant; a rootless
|
|
680
|
+
* member neither derives nor calls DH finish. False when the proof is not yet complete for this member, for
|
|
681
|
+
* example while the member's own approval of the registry update is outstanding.
|
|
682
|
+
*
|
|
683
|
+
* @throws ValidationError when the proof is outside the selected group issuance or changes the original Grant.
|
|
684
|
+
*/
|
|
685
|
+
function* acceptProof(deps, g, proof) {
|
|
686
|
+
const hab = g.party.group, { custodian } = g.party, grant = g.said;
|
|
687
|
+
const packet = parseBytes(proof);
|
|
688
|
+
const parent = packet.at(-1), child = parent?.nests?.[0];
|
|
689
|
+
if (!parent || !(parent.body instanceof SerderKERI) || parent.body.route !== MULTISIG_IPEX_PROOF_ROUTE
|
|
690
|
+
|| parent.body.pre !== custodian || !groupSigningMembers(deps.hby, hab.pre).includes(custodian)
|
|
691
|
+
|| JSON.stringify(parent.body.sad.a) !== JSON.stringify({ gid: hab.pre, grant })
|
|
692
|
+
|| parent.nests?.length !== 1 || !child)
|
|
693
|
+
throw new ValidationError("Proof wrapper is outside the selected group issuance");
|
|
694
|
+
// The core /multisig/ipex-proof route authenticates the wrapper and indexes it without replay.
|
|
695
|
+
yield* deps.reactor.processBytes(proof);
|
|
696
|
+
if (!deps.exc.verifyAccepted(parent.body.said, MULTISIG_IPEX_PROOF_ROUTE)) {
|
|
697
|
+
throw new ValidationError("Group proof wrapper is not authenticated");
|
|
698
|
+
}
|
|
699
|
+
const input = deps.exc.retainedInput(grant, { pending: true });
|
|
700
|
+
if (!input || !bytesEqual(child.body.raw, input.serder.raw) || child.nests?.length !== input.nests?.length) {
|
|
701
|
+
throw new ValidationError("Group proof changed the original live Grant");
|
|
702
|
+
}
|
|
703
|
+
const original = parseBytes(messagize(input.serder, { tsgs: input.tsgs, cigars: input.cigars, gvrsn: Vrsn_2_0 }))[0];
|
|
704
|
+
if (!bytesEqual(yield* wire({ ...child, nests: [] }), yield* wire(original))) {
|
|
705
|
+
throw new ValidationError("Group proof changed original issuer authentication");
|
|
706
|
+
}
|
|
707
|
+
for (const [index, old] of (input.nests ?? []).entries()) {
|
|
708
|
+
const next = child.nests[index], fresh = index === 0;
|
|
709
|
+
if (!bytesEqual(next.body.raw, old.body.raw) || next.nests?.length
|
|
710
|
+
|| next.attachments.length !== old.attachments.length + (fresh ? 1 : 0)
|
|
711
|
+
|| old.attachments.some((atc, i) => !bytesEqual(atc.raw, next.attachments[i].raw)))
|
|
712
|
+
throw new ValidationError("Group proof changed retained disclosure or attachments");
|
|
713
|
+
if (!fresh)
|
|
714
|
+
continue;
|
|
715
|
+
const node = next.body, added = envelopeFromFrame({ ...next, attachments: [next.attachments.at(-1)] });
|
|
716
|
+
const opening = added.bsqs[0], chain = node.regid && acceptedRegistryEvidence(deps.rgy.store, node.regid);
|
|
717
|
+
if (added.attachmentGroups.length !== 1
|
|
718
|
+
|| added.attachmentGroups[0].name.replace(/^Big/, "") !== "BlindedStateQuadruples"
|
|
719
|
+
|| added.bsqs.length !== 1 || added.bsss.length || !opening || opening.ts.label !== "issued"
|
|
720
|
+
|| opening.td.nonce !== node.said
|
|
721
|
+
|| !chain || !chain.updates.length || chain.updates.at(-1).sad.b !== opening.d.nonce)
|
|
722
|
+
return false;
|
|
723
|
+
const result = vet({ ...chain, db: deps.hby.db, acdc: node, blinder: opening });
|
|
724
|
+
if (result.kind !== "verified" || result.state.state !== "issued") {
|
|
725
|
+
throw new ValidationError("Final proof does not verify the current issued BUP");
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
yield* deps.exc.acceptPendingGrantProof({ ...input, nests: child.nests });
|
|
729
|
+
return deps.exc.complete(grant);
|
|
730
|
+
}
|
|
731
|
+
/** Take a stored final proof into the operation, scanned at most once a second. True once the Grant is accepted. */
|
|
732
|
+
function* scanProof(deps, g) {
|
|
733
|
+
const now = deps.clock.now();
|
|
734
|
+
if (now < g.nextProofScan)
|
|
735
|
+
return false;
|
|
736
|
+
g.nextProofScan = now + PROOF_SCAN_MS;
|
|
737
|
+
for (const [, parent] of [...deps.hby.db.exns.getTopItemIter()]) {
|
|
738
|
+
const attrs = parent.sad?.a;
|
|
739
|
+
if (parent.route !== MULTISIG_IPEX_PROOF_ROUTE || parent.pre !== g.party.custodian
|
|
740
|
+
|| attrs?.gid !== g.party.group.pre || attrs?.grant !== g.said)
|
|
741
|
+
continue;
|
|
742
|
+
if (yield* acceptProof(deps, g, yield* storedBytes(deps.exc, parent.said)))
|
|
743
|
+
return true;
|
|
744
|
+
}
|
|
745
|
+
return false;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Advance one group operation by one pass; true once the Grant is accepted and nothing is left to do. Mirrors
|
|
749
|
+
* the order of the loop it replaces: own proposal, Grant authentication, DH approval, the custodian's evidence and
|
|
750
|
+
* query, wrapper intake and contribution, outgoing DH messages, the custodian's registry update, finish and proof.
|
|
751
|
+
* Every signature here is one the consent that began the operation covers; the registry update's approval by
|
|
752
|
+
* other members is not.
|
|
753
|
+
*/
|
|
754
|
+
export function* stepGroup(ops, op) {
|
|
755
|
+
const g = op.group, { deps } = ops, { party } = g, { member, lead, coordinator, custodian } = party;
|
|
756
|
+
const hab = party.group, { grant, xid } = op, dh = op.dh, issuer = g.role === "issuer";
|
|
757
|
+
const send = function* (dest, msg, topic = "multisig") {
|
|
758
|
+
ops.enqueue(op, { src: member.pre, dest, msg, topic });
|
|
759
|
+
};
|
|
760
|
+
const broadcast = function* (msg) {
|
|
761
|
+
for (const peer of party.others())
|
|
762
|
+
yield* send(peer, msg);
|
|
763
|
+
};
|
|
764
|
+
if (deps.exc.complete(grant))
|
|
765
|
+
return true;
|
|
766
|
+
if (op.expires === null) {
|
|
767
|
+
const deadline = deps.exc.exchangeDeadline(g.grant);
|
|
768
|
+
if (deadline) {
|
|
769
|
+
op.deadline = deadline;
|
|
770
|
+
op.expires = timestampMicros(deadline);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
if (g.proposal) {
|
|
774
|
+
const proposal = g.proposal;
|
|
775
|
+
g.proposal = undefined;
|
|
776
|
+
yield* broadcast(proposal);
|
|
777
|
+
}
|
|
778
|
+
if (!g.started) {
|
|
779
|
+
g.started = true;
|
|
780
|
+
if (g.mode !== "proofOnly")
|
|
781
|
+
recover(deps, g);
|
|
782
|
+
}
|
|
783
|
+
if (g.mode === "proofOnly")
|
|
784
|
+
return yield* scanProof(deps, g);
|
|
785
|
+
if (!g.approved) {
|
|
786
|
+
if (!deps.exc.pendingGrant(grant, { xid, sender: g.grant.pre, recipient: g.grant.sad.ri })) {
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
dh.approve(grant, { coordinator, custodian });
|
|
790
|
+
g.approved = true;
|
|
791
|
+
ops.setPhase(op, "exchangingKeys");
|
|
792
|
+
}
|
|
793
|
+
if (lead && !g.nodeBytes && g.mode === "resumed")
|
|
794
|
+
g.nodeBytes = yield* nodeStream(g.nodes);
|
|
795
|
+
if (lead && !g.nodeBytes) {
|
|
796
|
+
g.nodeBytes = yield* nodeStream(g.nodes);
|
|
797
|
+
const input = deps.exc.retainedInput(grant, { pending: true });
|
|
798
|
+
const frame = parseBytes(messagize(input.serder, { tsgs: input.tsgs, cigars: input.cigars, gvrsn: Vrsn_2_0 }))[0];
|
|
799
|
+
frame.nests = [...input.nests ?? []];
|
|
800
|
+
const full = concatBytes(ops.discloser.proofSupport(g.nodeBytes, hab), yield* wire(frame));
|
|
801
|
+
for (const peer of party.others())
|
|
802
|
+
yield* send(peer, full);
|
|
803
|
+
if (issuer) {
|
|
804
|
+
yield* send(g.grant.sad.ri, full, "credential");
|
|
805
|
+
const child = parseBytes(dh.start(grant))[0];
|
|
806
|
+
g.frozen = child.body.raw;
|
|
807
|
+
yield* deps.protocol.dispatch(child, { local: true });
|
|
808
|
+
yield* broadcast(yield* party.wrap(child, MULTISIG_QRY_ROUTE));
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (!lead && (g.contributed || !issuer) && (yield* scanProof(deps, g)))
|
|
812
|
+
return true;
|
|
813
|
+
if (!(issuer && !lead && g.contributed)) {
|
|
814
|
+
for (const [said, item] of [...g.pending]) {
|
|
815
|
+
if (g.seen.has(said)) {
|
|
816
|
+
g.pending.delete(said);
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
if (item.wrapper.pre === member.pre) {
|
|
820
|
+
g.seen.add(said);
|
|
821
|
+
g.pending.delete(said);
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
if (!lead && !g.contributed) {
|
|
825
|
+
if (!issuer && !dh.hasPeerQuery(xid))
|
|
826
|
+
continue;
|
|
827
|
+
const signature = dh.contribute(xid, yield* wire(item.child));
|
|
828
|
+
g.frozen = item.child.body.raw;
|
|
829
|
+
g.contributed = true;
|
|
830
|
+
yield* deps.protocol.dispatch(item.child, { local: true });
|
|
831
|
+
const contribution = parseBytes(signature)[0];
|
|
832
|
+
yield* deps.protocol.dispatch(contribution, { local: true });
|
|
833
|
+
yield* broadcast(yield* party.wrap(contribution, issuer ? MULTISIG_QRY_ROUTE : MULTISIG_RPY_ROUTE));
|
|
834
|
+
if (issuer)
|
|
835
|
+
ops.setPhase(op, "awaitingAnchor");
|
|
836
|
+
}
|
|
837
|
+
else
|
|
838
|
+
yield* deps.protocol.dispatch(item.child, { local: true });
|
|
839
|
+
g.seen.add(said);
|
|
840
|
+
g.pending.delete(said);
|
|
841
|
+
if (issuer && !lead)
|
|
842
|
+
break;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
while (dh.outgoing.length) {
|
|
846
|
+
const message = dh.outgoing.pull();
|
|
847
|
+
if (message.kin === "sign") {
|
|
848
|
+
const child = parseBytes(message.msg)[0];
|
|
849
|
+
g.frozen = child.body.raw;
|
|
850
|
+
yield* deps.protocol.dispatch(child, { local: true });
|
|
851
|
+
yield* broadcast(yield* party.wrap(child, MULTISIG_RPY_ROUTE));
|
|
852
|
+
}
|
|
853
|
+
else if (message.kin === "query")
|
|
854
|
+
g.query = message.msg;
|
|
855
|
+
else
|
|
856
|
+
yield* send(message.dest, message.msg, "credential");
|
|
857
|
+
}
|
|
858
|
+
if (g.query && deps.clock.now() >= g.nextQuery && !dh.complete(xid)) {
|
|
859
|
+
yield* send(g.grant.sad.ri, g.query, "credential");
|
|
860
|
+
g.nextQuery = deps.clock.now() + (deps.queryResendMs ?? ISSUANCE_QUERY_RESEND_MS);
|
|
861
|
+
}
|
|
862
|
+
if (!lead || !dh.complete(xid))
|
|
863
|
+
return false;
|
|
864
|
+
const { rgy, receiptor, derived, hby } = deps, registry = op.registry;
|
|
865
|
+
if (issuer) {
|
|
866
|
+
if (!g.registryUpdate && rgy.store.headEvent(registry)?.ilk === "rip") {
|
|
867
|
+
const reg = rgy.regs.get(registry);
|
|
868
|
+
if (!reg || reg.hab !== hab)
|
|
869
|
+
throw new ValidationError("Issuer custodian requires its managed registry");
|
|
870
|
+
ops.setPhase(op, "awaitingAnchor");
|
|
871
|
+
const custody = new RegistryCustody({ hby, hab, registry: rgy.store, store: derived });
|
|
872
|
+
const staged = new RegistryAuthor(rgy, receiptor, custody).stage(reg, g.origin, "issued", {
|
|
873
|
+
auths: g.anchor?.auths,
|
|
874
|
+
});
|
|
875
|
+
// The other issuer members approve this exact registry update as a separate proposal.
|
|
876
|
+
const proposed = yield* proposeRegistryEvent(deps.group.registry, hab, staged.event, staged.opening, g.anchor);
|
|
877
|
+
g.registryUpdate = registryEventRef(staged.event, proposed.event);
|
|
878
|
+
}
|
|
879
|
+
if (g.registryUpdate) {
|
|
880
|
+
const read = { db: hby.db, store: rgy.store, protocol: deps.protocol };
|
|
881
|
+
if (registryEventStatus(read, g.registryUpdate).state !== "accepted") {
|
|
882
|
+
if ((yield* settleRegistryEvent(read, g.registryUpdate)).state !== "accepted")
|
|
883
|
+
return false;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
else if (rgy.store.headEvent(registry)?.ilk !== "bup")
|
|
888
|
+
return false;
|
|
889
|
+
ops.setPhase(op, "proving");
|
|
890
|
+
yield* dh.finish(xid);
|
|
891
|
+
if (!deps.exc.complete(grant))
|
|
892
|
+
throw new ValidationError("Issued group Grant remains unaccepted");
|
|
893
|
+
const completed = parseBytes(yield* storedBytes(deps.exc, grant))[0];
|
|
894
|
+
const support = ops.discloser.proofSupport(g.nodeBytes, hab);
|
|
895
|
+
// KERIpy form: the elected member shares the proof with the other members by mailbox.
|
|
896
|
+
const wrapped = yield* party.wrap(completed, MULTISIG_IPEX_PROOF_ROUTE);
|
|
897
|
+
// Kept locally, so the proof the members received is the one {@link groupIssuanceProof} exports.
|
|
898
|
+
yield* deps.reactor.processBytes(wrapped, { local: true });
|
|
899
|
+
const publicProof = concatBytes(support, wrapped);
|
|
900
|
+
for (const peer of party.others())
|
|
901
|
+
yield* send(peer, publicProof);
|
|
902
|
+
if (issuer)
|
|
903
|
+
yield* send(g.grant.sad.ri, support, "credential");
|
|
904
|
+
return true;
|
|
905
|
+
}
|