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
|
@@ -8,7 +8,7 @@ import { hkdf } from "@noble/hashes/hkdf";
|
|
|
8
8
|
import { sha256 } from "@noble/hashes/sha2";
|
|
9
9
|
import { Hab } from "../app/identity/habbing.js";
|
|
10
10
|
import { groupWitnesserSelection, isLocalGroupHab, latestGroupContribution, localGroupMember, } from "../app/multisig/group-members.js";
|
|
11
|
-
import { copyAuxiliaryQuery, envelopeFromFrame, } from "../app/protocol/parsering.js";
|
|
11
|
+
import { auxiliaryQueryWire, copyAuxiliaryQuery, envelopeFromFrame, restoreAuxiliaryQuery, } from "../app/protocol/parsering.js";
|
|
12
12
|
import { Deck } from "../core/deck.js";
|
|
13
13
|
import { ValidationError } from "../core/errors.js";
|
|
14
14
|
import { verifyQueryAttachments } from "../core/exchange/exchange-proofing.js";
|
|
@@ -85,6 +85,72 @@ export function deriveRoot(privateKey, context) {
|
|
|
85
85
|
shared.fill(0);
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* The DH transaction id of a Grant: its own SAID when it opens the exchange (`xip`), and otherwise the exchange it
|
|
90
|
+
* continues (`x`). The one definition, so the owner and any workflow that names the transaction agree.
|
|
91
|
+
*/
|
|
92
|
+
export function grantTransactionId(grant) {
|
|
93
|
+
return grant.ilk === "xip" ? grant.said : grant.sad.x;
|
|
94
|
+
}
|
|
95
|
+
/** The parties and transaction of a Grant `hab` takes part in, or null when it is not a DH party of it. */
|
|
96
|
+
function grantParties(hab, input) {
|
|
97
|
+
const message = input.serder, issuer = message.pre, issuee = message.sad.ri;
|
|
98
|
+
const xid = grantTransactionId(message);
|
|
99
|
+
return typeof xid === "string" && typeof issuee === "string" && issuer !== issuee
|
|
100
|
+
&& [issuer, issuee].includes(hab.pre)
|
|
101
|
+
? { issuer, issuee, xid }
|
|
102
|
+
: null;
|
|
103
|
+
}
|
|
104
|
+
/** Resolve the pending Grant `hab` may start DH for, or the reason it cannot. Reads only; creates no approval. */
|
|
105
|
+
function freshScope(hab, exc, grant) {
|
|
106
|
+
const input = exc.retainedInput(grant, { pending: true });
|
|
107
|
+
if (!input)
|
|
108
|
+
return "DH requires an authenticated pending Grant";
|
|
109
|
+
const parties = grantParties(hab, input);
|
|
110
|
+
if (!parties)
|
|
111
|
+
return "Local habitat is not a DH party";
|
|
112
|
+
const context = exc.pendingGrant(grant, { ...parties, sender: parties.issuer, recipient: parties.issuee });
|
|
113
|
+
if (!context)
|
|
114
|
+
return "DH requires authenticated fresh registry scope";
|
|
115
|
+
return { input, ...parties, registries: [...context.registries] };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The scope of a completed exchange that can finish without new key exchange: the Grant is still pending
|
|
119
|
+
* inside its original deadline and `hab` holds a root for the registries it discloses. The scope is those
|
|
120
|
+
* registries; a registry that is still fresh must hold one. No fresh registry is required, because the
|
|
121
|
+
* issuer's update may already be accepted: {@link Dhke.finish} proves the held root against that update, so
|
|
122
|
+
* a root from any other exchange is rejected there. Pure read of stores. Null when nothing can resume.
|
|
123
|
+
*/
|
|
124
|
+
export function resumableExchange({ hab, exc }, grant) {
|
|
125
|
+
const input = exc.retainedInput(grant, { pending: true });
|
|
126
|
+
const parties = input && grantParties(hab, input);
|
|
127
|
+
if (!input || !parties)
|
|
128
|
+
return null;
|
|
129
|
+
const disclosed = (input.nests ?? []).map((nest) => nest.body.ked?.rd);
|
|
130
|
+
const held = [...new Set(disclosed)].filter((registry) => typeof registry === "string" && hab.mgr.hasRegistryRoot(hab.pre, registry));
|
|
131
|
+
const fresh = exc.pendingGrant(grant, { ...parties, sender: parties.issuer, recipient: parties.issuee });
|
|
132
|
+
if (!held.length || fresh?.registries.some((registry) => !held.includes(registry)))
|
|
133
|
+
return null;
|
|
134
|
+
return { input, ...parties, registries: held };
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Whether `registry` has no committed state: its head is still the inception and no update is escrowed.
|
|
138
|
+
* A staged update waits in the missing-anchor or out-of-order escrow under its registry until the issuer's
|
|
139
|
+
* anchor is accepted, and an accepted one moves the head, so two keyed reads bounded by this registry
|
|
140
|
+
* decide it. Only then can a retained root be unused, because every derivation from a root, and every copy
|
|
141
|
+
* of it to another member (which `RegistryRoot` authorizes only for an accepted, issuer-anchored update),
|
|
142
|
+
* starts from a staged or accepted update. A remote update escrowed for this registry also counts, which
|
|
143
|
+
* is the conservative side.
|
|
144
|
+
*/
|
|
145
|
+
export function registryUncommitted(registryStore, registry) {
|
|
146
|
+
if (registryStore.headEvent(registry)?.ilk !== "rip")
|
|
147
|
+
return false;
|
|
148
|
+
for (const table of [registryStore.baser.maes, registryStore.baser.ooes]) {
|
|
149
|
+
for (const _ of table.getTopItemIter(registry))
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
88
154
|
/**
|
|
89
155
|
* Issuer/issuee DH for an explicitly approved pending Grant. Wire
|
|
90
156
|
* intake cannot approve work. Inject as ProtocolProcessor.qvy and register on
|
|
@@ -95,7 +161,7 @@ export function deriveRoot(privateKey, context) {
|
|
|
95
161
|
*/
|
|
96
162
|
export class Dhke {
|
|
97
163
|
/** Bind concrete local owners; construction grants no consent and writes nothing. */
|
|
98
|
-
constructor({ hab, exc, registryStore, rvy = hab.rvy, now = Date.now }) {
|
|
164
|
+
constructor({ hab, exc, registryStore, rvy = hab.rvy, now = Date.now, kram = true }) {
|
|
99
165
|
Object.defineProperty(this, "resource", {
|
|
100
166
|
enumerable: true,
|
|
101
167
|
configurable: true,
|
|
@@ -108,6 +174,13 @@ export class Dhke {
|
|
|
108
174
|
writable: true,
|
|
109
175
|
value: new Deck()
|
|
110
176
|
});
|
|
177
|
+
/** Completion cues for roots just retained by this group's custodian; feed `RootReplicator.replicate`. */
|
|
178
|
+
Object.defineProperty(this, "replicated", {
|
|
179
|
+
enumerable: true,
|
|
180
|
+
configurable: true,
|
|
181
|
+
writable: true,
|
|
182
|
+
value: new Deck()
|
|
183
|
+
});
|
|
111
184
|
Object.defineProperty(this, "pendingQueries", {
|
|
112
185
|
enumerable: true,
|
|
113
186
|
configurable: true,
|
|
@@ -156,6 +229,13 @@ export class Dhke {
|
|
|
156
229
|
writable: true,
|
|
157
230
|
value: void 0
|
|
158
231
|
});
|
|
232
|
+
Object.defineProperty(this, "kram", {
|
|
233
|
+
enumerable: true,
|
|
234
|
+
configurable: true,
|
|
235
|
+
writable: true,
|
|
236
|
+
value: void 0
|
|
237
|
+
});
|
|
238
|
+
this.kram = kram;
|
|
159
239
|
if (!(hab instanceof Hab) || hab.db !== exc.hby.db || rvy.db !== hab.db
|
|
160
240
|
|| exc.hby.habs.get(hab.pre) !== hab)
|
|
161
241
|
throw new ValidationError("DH requires a local signing habitat");
|
|
@@ -169,8 +249,20 @@ export class Dhke {
|
|
|
169
249
|
throw new ValidationError("DH requires a local signing member");
|
|
170
250
|
}
|
|
171
251
|
}
|
|
172
|
-
/**
|
|
252
|
+
/**
|
|
253
|
+
* Select one live fresh issuance and freeze its exact bodies, parties and registry order.
|
|
254
|
+
*
|
|
255
|
+
* A registry that already holds a root refuses (`Registry root conflicts with this live DH operation`)
|
|
256
|
+
* unless it has no committed state ({@link registryUncommitted}): then the held root was never used and
|
|
257
|
+
* this exchange, whose Grant is from the registry's issuer, overwrites it when it retains its own root,
|
|
258
|
+
* never at approval, so an interruption before retention leaves the old root. Once any update is staged
|
|
259
|
+
* or accepted the refusal is unchanged. A KERIpy peer keeps the old rule and refuses such a second
|
|
260
|
+
* exchange: with a KERIpy holder the issuer needs a fresh registry. One process also refuses a second
|
|
261
|
+
* live operation on a registry, so two pending Grants cannot clobber each other's root.
|
|
262
|
+
*/
|
|
173
263
|
approve(grant, { coordinator, custodian } = {}) {
|
|
264
|
+
if (!this.kram)
|
|
265
|
+
throw new ValidationError("DH requires KRAM: enable it with the runtime's kram option");
|
|
174
266
|
this.pruneQueries();
|
|
175
267
|
this.hab.mgr.requireRegistryRootAccess();
|
|
176
268
|
for (const op of this.operations.values()) {
|
|
@@ -184,27 +276,68 @@ export class Dhke {
|
|
|
184
276
|
return op.xid;
|
|
185
277
|
}
|
|
186
278
|
}
|
|
187
|
-
const
|
|
188
|
-
if (
|
|
189
|
-
throw new ValidationError(
|
|
190
|
-
const
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
279
|
+
const scope = freshScope(this.hab, this.exc, grant);
|
|
280
|
+
if (typeof scope === "string")
|
|
281
|
+
throw new ValidationError(scope);
|
|
282
|
+
const op = this.frozen(scope, grant, { coordinator, custodian }, false);
|
|
283
|
+
for (const other of this.operations.values()) {
|
|
284
|
+
if (!other.abandoned && other.registries.some((registry) => op.registries.includes(registry))) {
|
|
285
|
+
throw new ValidationError("Another live DH operation uses this registry");
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
for (const registry of op.registries) {
|
|
289
|
+
if (this.hab.mgr.hasRegistryRoot(this.hab.pre, registry) && registryUncommitted(this.registryStore, registry)) {
|
|
290
|
+
op.replaces.add(registry);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
this.roots(op);
|
|
294
|
+
this.operations.set(op.xid, op);
|
|
295
|
+
this.replayQuery(op.xid);
|
|
296
|
+
return op.xid;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Resume a completed exchange after a process restart, without new key exchange: rebuild the operation as
|
|
300
|
+
* completed from the roots held for the Grant's registries ({@link resumableExchange}). It holds no
|
|
301
|
+
* ephemeral key, so it cannot start, answer or renew a query; it can only {@link complete} and
|
|
302
|
+
* {@link finish}, which signs nothing and verifies the issuer's update against the held root. Needs an
|
|
303
|
+
* unlocked Keeper; wire intake never calls it. Repeating it returns the same transaction.
|
|
304
|
+
*
|
|
305
|
+
* @throws ValidationError unless the Grant is pending and holds a root for each registry in its scope.
|
|
306
|
+
*/
|
|
307
|
+
resume(grant, { coordinator, custodian } = {}) {
|
|
308
|
+
this.pruneQueries();
|
|
309
|
+
this.hab.mgr.requireRegistryRootAccess();
|
|
310
|
+
for (const op of this.operations.values()) {
|
|
311
|
+
if (op.grant !== grant)
|
|
312
|
+
continue;
|
|
313
|
+
if (!op.completed)
|
|
314
|
+
throw new ValidationError("DH is still exchanging keys for this Grant; nothing to resume");
|
|
315
|
+
this.pending(op);
|
|
316
|
+
return op.xid;
|
|
317
|
+
}
|
|
318
|
+
const scope = resumableExchange(this, grant);
|
|
319
|
+
if (!scope)
|
|
320
|
+
throw new ValidationError("No root is held for this pending Grant's registries");
|
|
321
|
+
const op = this.frozen(scope, grant, { coordinator, custodian }, true);
|
|
322
|
+
this.roots(op);
|
|
323
|
+
this.operations.set(op.xid, op);
|
|
324
|
+
this.pendingQueries.delete(op.xid);
|
|
325
|
+
this.dropHeld(op.xid);
|
|
326
|
+
return op.xid;
|
|
327
|
+
}
|
|
328
|
+
/** Freeze the exact pending Grant, parties and registry order into an operation; registers nothing. */
|
|
329
|
+
frozen(scope, grant, { coordinator, custodian }, completed) {
|
|
330
|
+
const { input, issuer, issuee, xid, registries } = scope;
|
|
195
331
|
if (this.operations.has(xid))
|
|
196
332
|
throw new ValidationError("Another Grant is selected for this transaction");
|
|
197
|
-
const context = this.exc.pendingGrant(grant, { xid, sender: issuer, recipient: issuee });
|
|
198
|
-
if (!context)
|
|
199
|
-
throw new ValidationError("DH requires authenticated fresh registry scope");
|
|
200
333
|
const op = {
|
|
201
334
|
xid,
|
|
202
335
|
grant,
|
|
203
336
|
issuer,
|
|
204
337
|
issuee,
|
|
205
|
-
raw:
|
|
338
|
+
raw: input.serder.raw.slice(),
|
|
206
339
|
bodies: (input.nests ?? []).map((nest) => nest.body.raw.slice()),
|
|
207
|
-
registries
|
|
340
|
+
registries,
|
|
208
341
|
privateKeys: new Map(),
|
|
209
342
|
publicKeys: {},
|
|
210
343
|
peer: null,
|
|
@@ -213,7 +346,8 @@ export class Dhke {
|
|
|
213
346
|
proposal: null,
|
|
214
347
|
contribution: null,
|
|
215
348
|
custody: null,
|
|
216
|
-
|
|
349
|
+
replaces: new Set(),
|
|
350
|
+
completed,
|
|
217
351
|
abandoned: false,
|
|
218
352
|
};
|
|
219
353
|
if (this.group) {
|
|
@@ -235,10 +369,17 @@ export class Dhke {
|
|
|
235
369
|
else if (coordinator !== undefined || custodian !== undefined) {
|
|
236
370
|
throw new ValidationError("Individual DH has no group coordinator");
|
|
237
371
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
372
|
+
return op;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Whether this owner already holds a live operation for `grant`. Two operation tables over one runtime
|
|
376
|
+
* (the host's and a command's own) use it so only the first to reach a Grant drives it.
|
|
377
|
+
*/
|
|
378
|
+
holds(grant) {
|
|
379
|
+
for (const op of this.operations.values())
|
|
380
|
+
if (op.grant === grant && !op.abandoned)
|
|
381
|
+
return true;
|
|
382
|
+
return false;
|
|
242
383
|
}
|
|
243
384
|
/** Return the stable issuer proposal until own threshold intake supplies the complete query. */
|
|
244
385
|
start(grant) {
|
|
@@ -506,12 +647,34 @@ export class Dhke {
|
|
|
506
647
|
/** Drop local ephemeral key references and permanently refuse this operation. */
|
|
507
648
|
abandon(xid) {
|
|
508
649
|
const retained = this.pendingQueries.delete(xid);
|
|
650
|
+
this.dropHeld(xid);
|
|
509
651
|
if (retained && !this.operations.has(xid))
|
|
510
652
|
return;
|
|
511
653
|
const op = this.operation(xid);
|
|
512
654
|
this.clearPrivate(op);
|
|
513
655
|
op.abandoned = true;
|
|
514
656
|
}
|
|
657
|
+
/**
|
|
658
|
+
* Whether this local habitat is the party of DH transaction `xid` with `sender`.
|
|
659
|
+
*
|
|
660
|
+
* Messages carry no recipient, so a per-identifier registry selects the owner by transaction:
|
|
661
|
+
* an approved operation here, a stored Grant from `sender` addressed to this habitat, or, for a
|
|
662
|
+
* group, its own members' contributions, which KRAM must pool before this owner consents.
|
|
663
|
+
*/
|
|
664
|
+
inTransaction(sender, xid) {
|
|
665
|
+
return this.operations.has(xid) || (this.group && sender === this.hab.pre)
|
|
666
|
+
|| Dhke.recipients(this.hab.db, sender, xid).includes(this.hab.pre);
|
|
667
|
+
}
|
|
668
|
+
/** Recipients of the pending Grants `sender` opened in DH transaction `xid`, from KRAM-accepted store rows. */
|
|
669
|
+
static recipients(db, sender, xid) {
|
|
670
|
+
const recipients = [];
|
|
671
|
+
for (const [key] of db.tmsc.getTopItemIter([sender, xid])) {
|
|
672
|
+
const recipient = db.epse.get([key[2]])?.sad?.ri;
|
|
673
|
+
if (typeof recipient === "string")
|
|
674
|
+
recipients.push(recipient);
|
|
675
|
+
}
|
|
676
|
+
return recipients;
|
|
677
|
+
}
|
|
515
678
|
/** Release all process-local approvals and retry bytes; zero owned ephemeral scalar arrays. */
|
|
516
679
|
close() {
|
|
517
680
|
for (const op of this.operations.values())
|
|
@@ -519,6 +682,7 @@ export class Dhke {
|
|
|
519
682
|
this.operations.clear();
|
|
520
683
|
this.pendingQueries.clear();
|
|
521
684
|
this.outgoing.clear();
|
|
685
|
+
this.replicated.clear();
|
|
522
686
|
}
|
|
523
687
|
/** Retain public query evidence against one exact pending Grant, without creating keys or consent. */
|
|
524
688
|
retainQuery(envelope) {
|
|
@@ -540,11 +704,22 @@ export class Dhke {
|
|
|
540
704
|
for (const [, key] of entries)
|
|
541
705
|
decodePublicKey(key);
|
|
542
706
|
const expires = this.retainUntil(envelope, grant);
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
707
|
+
// After a restart the persisted copy is the prior one, so a successor is judged as it is before one.
|
|
708
|
+
const prior = this.pendingQueries.get(data.x) ?? this.loadHeld(data.x, grant.said);
|
|
709
|
+
if (prior && !bytesEqual(prior.envelope.serder.raw, query.raw)) {
|
|
710
|
+
// The issuer re-dates a query that aged before this side consented (`requery`): same sender, keys and
|
|
711
|
+
// Grant under a new date. It replaces the held one; any other difference is a conflict.
|
|
712
|
+
const held = this.parameters(prior.envelope.serder, "q");
|
|
713
|
+
const same = prior.envelope.serder.pre === query.pre && prior.grant === grant.said
|
|
714
|
+
&& JSON.stringify(Object.entries(held.pk).sort()) === JSON.stringify(entries.sort());
|
|
715
|
+
if (!same)
|
|
716
|
+
throw new ValidationError("Conflicting DH query");
|
|
717
|
+
}
|
|
718
|
+
if (!prior || !bytesEqual(prior.envelope.serder.raw, query.raw)) {
|
|
547
719
|
this.pendingQueries.set(data.x, { envelope: copyAuxiliaryQuery(envelope), expires, grant: grant.said });
|
|
720
|
+
// KRAM now holds this query as seen, so a resend of it is a replay: the held copy must outlive this process.
|
|
721
|
+
if (!envelope.nests.length)
|
|
722
|
+
this.hab.db.heldq.pin([data.x], auxiliaryQueryWire(envelope));
|
|
548
723
|
}
|
|
549
724
|
}
|
|
550
725
|
/** Expired evidence cannot survive another owner interaction. */
|
|
@@ -557,11 +732,32 @@ export class Dhke {
|
|
|
557
732
|
}
|
|
558
733
|
/** Consume the first authenticated request only after explicit approval; never renew either clock. */
|
|
559
734
|
replayQuery(xid) {
|
|
560
|
-
const
|
|
735
|
+
const grant = this.operations.get(xid)?.grant;
|
|
736
|
+
const pending = this.pendingQueries.get(xid) ?? (grant ? this.loadHeld(xid, grant) : undefined);
|
|
561
737
|
this.pendingQueries.delete(xid);
|
|
562
|
-
|
|
563
|
-
|
|
738
|
+
this.dropHeld(xid);
|
|
739
|
+
if (pending && pending.grant === grant && BigInt(this.now()) * 1000n < pending.expires) {
|
|
564
740
|
this.processQuery(pending.envelope);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
/** The persisted query for `xid` after a restart, with its lifetime recomputed from KRAM's durable records. */
|
|
744
|
+
loadHeld(xid, grant) {
|
|
745
|
+
const wire = this.hab.db.heldq.get([xid]);
|
|
746
|
+
const input = this.exc.retainedInput(grant, { pending: true });
|
|
747
|
+
if (!wire || !input)
|
|
748
|
+
return undefined;
|
|
749
|
+
try {
|
|
750
|
+
const envelope = restoreAuxiliaryQuery(wire);
|
|
751
|
+
return { envelope, expires: this.retainUntil(envelope, input.serder), grant };
|
|
752
|
+
}
|
|
753
|
+
catch (error) {
|
|
754
|
+
if (error instanceof ValidationError)
|
|
755
|
+
return undefined;
|
|
756
|
+
throw error;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
dropHeld(xid) {
|
|
760
|
+
this.hab.db.heldq.rem([xid]);
|
|
565
761
|
}
|
|
566
762
|
/** Bound process-local public evidence by the original query and accepted Grant lifetimes. */
|
|
567
763
|
retainUntil(envelope, grant) {
|
|
@@ -610,8 +806,12 @@ export class Dhke {
|
|
|
610
806
|
}
|
|
611
807
|
roots(op) {
|
|
612
808
|
for (const registry of op.registries) {
|
|
613
|
-
|
|
614
|
-
|
|
809
|
+
const held = this.hab.mgr.hasRegistryRoot(this.hab.pre, registry);
|
|
810
|
+
if (held !== op.completed && !(held && op.replaces.has(registry))) {
|
|
811
|
+
throw new ValidationError(held
|
|
812
|
+
? "Registry root conflicts with this live DH operation: a DH-issued registry must not have a custody root, "
|
|
813
|
+
+ "so do not call createCustody for it before issuance (use a registry without custody)"
|
|
814
|
+
: "Registry root conflicts with this live DH operation");
|
|
615
815
|
}
|
|
616
816
|
}
|
|
617
817
|
}
|
|
@@ -648,6 +848,9 @@ export class Dhke {
|
|
|
648
848
|
return { op, peer };
|
|
649
849
|
}
|
|
650
850
|
generate(op) {
|
|
851
|
+
// A completed operation, including a resumed one, never holds or makes ephemeral keys again.
|
|
852
|
+
if (op.completed)
|
|
853
|
+
throw new ValidationError("DH operation has already completed");
|
|
651
854
|
this.custody(op, true);
|
|
652
855
|
if (op.privateKeys.size)
|
|
653
856
|
return;
|
|
@@ -675,11 +878,20 @@ export class Dhke {
|
|
|
675
878
|
]));
|
|
676
879
|
}
|
|
677
880
|
retain(op, peer, roots) {
|
|
678
|
-
for (const [registry, root] of roots)
|
|
679
|
-
|
|
881
|
+
for (const [registry, root] of roots) {
|
|
882
|
+
const replacing = op.replaces.has(registry);
|
|
883
|
+
// Recheck at commit time, from stores: the held root must still be unused.
|
|
884
|
+
if (replacing && !registryUncommitted(this.registryStore, registry)) {
|
|
885
|
+
throw new ValidationError("Registry root conflicts with this live DH operation");
|
|
886
|
+
}
|
|
887
|
+
this.hab.mgr.putRegistryRoot(this.hab.pre, registry, root, replacing);
|
|
888
|
+
}
|
|
680
889
|
op.peer = { ...peer };
|
|
681
890
|
op.completed = true;
|
|
682
891
|
this.clearPrivate(op);
|
|
892
|
+
// Every current member must hold every root; the custodian replicates.
|
|
893
|
+
if (this.group)
|
|
894
|
+
this.replicated.push({ gid: this.hab.pre, registries: [...roots.keys()] });
|
|
683
895
|
}
|
|
684
896
|
clearPrivate(op) {
|
|
685
897
|
for (const key of op.privateKeys.values())
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/** Public ACDC registry database layout and joined LMDB lifecycle. */
|
|
2
2
|
import { Diger, NumberPrimitive, Saider, SerderACDC } from "cesr-ts";
|
|
3
|
+
import { DerivedUpdateRecord } from "../core/records.js";
|
|
3
4
|
import { StoreOwner } from "../db/core/store-owner.js";
|
|
5
|
+
import { Komer } from "../db/koming.js";
|
|
4
6
|
import { B64OnIoSetSuber, BytesSuber, CatCesrSuber, CesrOnSuber, CesrSuber, SerderSuber } from "../db/subing.js";
|
|
5
7
|
/**
|
|
6
8
|
* Own one environment for public registry TEL bodies, accepted ordinals and heads.
|
|
@@ -39,6 +41,13 @@ export class RegBaser extends StoreOwner {
|
|
|
39
41
|
writable: true,
|
|
40
42
|
value: void 0
|
|
41
43
|
});
|
|
44
|
+
/** keri-ts only. BUP SAID -> public blind-derivation coordinates; never a salt or opening. */
|
|
45
|
+
Object.defineProperty(this, "dvus", {
|
|
46
|
+
enumerable: true,
|
|
47
|
+
configurable: true,
|
|
48
|
+
writable: true,
|
|
49
|
+
value: void 0
|
|
50
|
+
});
|
|
42
51
|
/** Event-SAID -> earliest verified KEL source couple, in the current store. */
|
|
43
52
|
Object.defineProperty(this, "ancs", {
|
|
44
53
|
enumerable: true,
|
|
@@ -71,6 +80,7 @@ export class RegBaser extends StoreOwner {
|
|
|
71
80
|
this.tels = new CesrOnSuber(this.store, { subkey: "tels.", ctor: Saider });
|
|
72
81
|
this.heads = new CesrSuber(this.store, { subkey: "heds.", ctor: Saider });
|
|
73
82
|
this.openings = new BytesSuber(this.store, { subkey: "openings." });
|
|
83
|
+
this.dvus = new Komer(this.store, { subkey: "dvus.", recordClass: DerivedUpdateRecord });
|
|
74
84
|
this.ancs = new CatCesrSuber(this.store, { subkey: "ancs.", ctor: [NumberPrimitive, Diger] });
|
|
75
85
|
this.maes = new B64OnIoSetSuber(this.store, { subkey: "maes." });
|
|
76
86
|
this.ooes = new B64OnIoSetSuber(this.store, { subkey: "ooes." });
|
|
@@ -177,7 +177,7 @@ export function rootExn(hab, recipient, gid, registry, root, { event, td }) {
|
|
|
177
177
|
* keeps the copy escrowed rather than refusing it.
|
|
178
178
|
*/
|
|
179
179
|
export class RegistryRoot {
|
|
180
|
-
constructor(hby, rgy, notifier = null) {
|
|
180
|
+
constructor(hby, rgy, notifier = null, changes) {
|
|
181
181
|
Object.defineProperty(this, "resource", {
|
|
182
182
|
enumerable: true,
|
|
183
183
|
configurable: true,
|
|
@@ -208,11 +208,19 @@ export class RegistryRoot {
|
|
|
208
208
|
writable: true,
|
|
209
209
|
value: void 0
|
|
210
210
|
});
|
|
211
|
+
/** Told `{kind: "root", id: registry}` after a copy is retained, so an embedder that waited for the root can act. */
|
|
212
|
+
Object.defineProperty(this, "changes", {
|
|
213
|
+
enumerable: true,
|
|
214
|
+
configurable: true,
|
|
215
|
+
writable: true,
|
|
216
|
+
value: void 0
|
|
217
|
+
});
|
|
211
218
|
if (rgy.hby !== hby)
|
|
212
219
|
throw new ValidationError("registry root replication requires the Habery's registry owner");
|
|
213
220
|
this.hby = hby;
|
|
214
221
|
this.rgy = rgy;
|
|
215
222
|
this.notifier = notifier;
|
|
223
|
+
this.changes = changes;
|
|
216
224
|
if (!hby.readonly)
|
|
217
225
|
this.reconcile();
|
|
218
226
|
}
|
|
@@ -371,6 +379,11 @@ export class RegistryRoot {
|
|
|
371
379
|
}
|
|
372
380
|
return found.sort((left, right) => left[0] - right[0]).map(([, aid]) => aid);
|
|
373
381
|
}
|
|
382
|
+
/** Public key by which `member` currently contributes to `gid`, or null while not a known current signing member. */
|
|
383
|
+
contributor(gid, member) {
|
|
384
|
+
const gkever = this.hby.db.getKever(gid);
|
|
385
|
+
return gkever ? contribution(this.hby.db, member, gkever.verfers)?.key ?? null : null;
|
|
386
|
+
}
|
|
374
387
|
/** Return the registry SAIDs with a root retained locally for `gid`. */
|
|
375
388
|
held(gid) {
|
|
376
389
|
const found = [];
|
|
@@ -691,6 +704,8 @@ export class RegistryRoot {
|
|
|
691
704
|
rd: registry,
|
|
692
705
|
});
|
|
693
706
|
}
|
|
707
|
+
if (added)
|
|
708
|
+
this.changes?.push({ kind: "root", id: registry });
|
|
694
709
|
return added;
|
|
695
710
|
}
|
|
696
711
|
/** Try one escrowed copy: true retained, null still pending, false dropped. */
|
|
@@ -286,72 +286,111 @@ class SchemaResources {
|
|
|
286
286
|
return root;
|
|
287
287
|
}
|
|
288
288
|
}
|
|
289
|
+
/** Most closures kept per store; the oldest is evicted first. */
|
|
290
|
+
const COMPILED_SCHEMA_BOUND = 256;
|
|
291
|
+
/**
|
|
292
|
+
* Compiled closures by store, then root SAID. A closure is fully determined by its root SAID: the root and every
|
|
293
|
+
* bundled or `$ref` resource is verified against its SAID when admitted, so a cached closure cannot go stale.
|
|
294
|
+
* Per store, so a store that does not hold the schema still reports it missing; only a valid closure is kept.
|
|
295
|
+
*/
|
|
296
|
+
const compiledSchemas = new WeakMap();
|
|
297
|
+
let compilations = 0;
|
|
298
|
+
/** How many schema closures this process has compiled; a diagnostic for tests of the cache. */
|
|
299
|
+
export function schemaCompilationCount() {
|
|
300
|
+
return compilations;
|
|
301
|
+
}
|
|
302
|
+
function isCompiled(value) {
|
|
303
|
+
return "validate" in value;
|
|
304
|
+
}
|
|
305
|
+
/** Compile an admitted closure once; regular-expression and duplicate-id refusals are normal invalid outcomes. */
|
|
306
|
+
function compileClosure(resources, root) {
|
|
307
|
+
const { roots, scopes } = resources;
|
|
308
|
+
// Compile only admitted private copies. Canonical local URNs remove URI-alias ambiguity without changing stored IDs.
|
|
309
|
+
const uri = (said) => `urn:said:${said}`;
|
|
310
|
+
const registered = new Set();
|
|
311
|
+
const copy = (value) => {
|
|
312
|
+
if (Array.isArray(value))
|
|
313
|
+
return value.map(copy);
|
|
314
|
+
if (!object(value))
|
|
315
|
+
return value;
|
|
316
|
+
const result = Object.fromEntries(Object.entries(value).map(([key, item]) => [key, copy(item)]));
|
|
317
|
+
const owner = scopes.get(value);
|
|
318
|
+
if (!owner)
|
|
319
|
+
return result;
|
|
320
|
+
if ("$id" in value) {
|
|
321
|
+
if (registered.has(owner.said))
|
|
322
|
+
delete result.$id;
|
|
323
|
+
else {
|
|
324
|
+
result.$id = uri(owner.said);
|
|
325
|
+
registered.add(owner.said);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if ("$ref" in value) {
|
|
329
|
+
const parsed = reference(value.$ref);
|
|
330
|
+
result.$ref = uri(parsed.said ?? owner.said) + parsed.fragment;
|
|
331
|
+
}
|
|
332
|
+
// Python treats these AJV extension/obsolete applicator keywords as annotations.
|
|
333
|
+
for (const key of ["$async", "nullable", "dependencies", "additionalItems"])
|
|
334
|
+
delete result[key];
|
|
335
|
+
return result;
|
|
336
|
+
};
|
|
337
|
+
const validator = new ValidatorCtor({ strict: false, validateFormats: false });
|
|
338
|
+
for (const schema of scopes.keys()) {
|
|
339
|
+
if (!validator.validateSchema(schema))
|
|
340
|
+
throw new InvalidSchema("Invalid Draft 2020-12 schema");
|
|
341
|
+
}
|
|
342
|
+
const copies = roots.map(copy);
|
|
343
|
+
let validate;
|
|
344
|
+
try {
|
|
345
|
+
for (const schema of copies)
|
|
346
|
+
validator.addSchema(schema);
|
|
347
|
+
validate = validator.getSchema(uri(root.said));
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
if (error instanceof SyntaxError && error.message.includes("regular expression")) {
|
|
351
|
+
return { kind: "invalid", subject: "schema", reason: error.message };
|
|
352
|
+
}
|
|
353
|
+
if (error instanceof Error && /^reference "urn:said:[^"]+" resolves to more than one schema$/.test(error.message)) {
|
|
354
|
+
return { kind: "invalid", subject: "schema", reason: error.message };
|
|
355
|
+
}
|
|
356
|
+
throw error;
|
|
357
|
+
}
|
|
358
|
+
if (!validate)
|
|
359
|
+
throw new Error("Admitted schema was not registered");
|
|
360
|
+
compilations++;
|
|
361
|
+
return { validator, validate };
|
|
362
|
+
}
|
|
289
363
|
/** Local static schema closure plus a synchronous Draft 2020-12 validator; legacy Schemer behavior is unchanged. */
|
|
290
364
|
export function validateSchema(acdc, db, override) {
|
|
291
|
-
const resources = new SchemaResources(db);
|
|
292
365
|
try {
|
|
366
|
+
// The closure is always admitted: that verifies every SAID, and the verified root SAID names the cached validator.
|
|
367
|
+
const resources = new SchemaResources(db);
|
|
293
368
|
const root = resources.admit(override ?? acdc.schema);
|
|
294
369
|
if (!root) {
|
|
295
370
|
return resources.missing
|
|
296
371
|
? { kind: "missing", said: resources.missing }
|
|
297
372
|
: { kind: "invalid", subject: "schema", reason: "Invalid ACDC schema field" };
|
|
298
373
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
delete result.$id;
|
|
315
|
-
else {
|
|
316
|
-
result.$id = uri(owner.said);
|
|
317
|
-
registered.add(owner.said);
|
|
318
|
-
}
|
|
374
|
+
// An override object is checked against the document it replaces each time and is never kept.
|
|
375
|
+
const keep = typeof override !== "object";
|
|
376
|
+
let store = compiledSchemas.get(db);
|
|
377
|
+
let compiled = keep ? store?.get(root.said) : undefined;
|
|
378
|
+
if (!compiled) {
|
|
379
|
+
const built = compileClosure(resources, root);
|
|
380
|
+
if (!isCompiled(built))
|
|
381
|
+
return built;
|
|
382
|
+
compiled = built;
|
|
383
|
+
if (keep) {
|
|
384
|
+
if (!store)
|
|
385
|
+
compiledSchemas.set(db, store = new Map());
|
|
386
|
+
if (store.size >= COMPILED_SCHEMA_BOUND)
|
|
387
|
+
store.delete(store.keys().next().value);
|
|
388
|
+
store.set(root.said, compiled);
|
|
319
389
|
}
|
|
320
|
-
if ("$ref" in value) {
|
|
321
|
-
const parsed = reference(value.$ref);
|
|
322
|
-
result.$ref = uri(parsed.said ?? owner.said) + parsed.fragment;
|
|
323
|
-
}
|
|
324
|
-
// Python treats these AJV extension/obsolete applicator keywords as annotations.
|
|
325
|
-
for (const key of ["$async", "nullable", "dependencies", "additionalItems"])
|
|
326
|
-
delete result[key];
|
|
327
|
-
return result;
|
|
328
|
-
};
|
|
329
|
-
const validator = new ValidatorCtor({ strict: false, validateFormats: false });
|
|
330
|
-
for (const schema of scopes.keys()) {
|
|
331
|
-
if (!validator.validateSchema(schema))
|
|
332
|
-
throw new InvalidSchema("Invalid Draft 2020-12 schema");
|
|
333
|
-
}
|
|
334
|
-
const copies = roots.map(copy);
|
|
335
|
-
let validate;
|
|
336
|
-
try {
|
|
337
|
-
for (const schema of copies)
|
|
338
|
-
validator.addSchema(schema);
|
|
339
|
-
validate = validator.getSchema(uri(root.said));
|
|
340
|
-
}
|
|
341
|
-
catch (error) {
|
|
342
|
-
if (error instanceof SyntaxError && error.message.includes("regular expression")) {
|
|
343
|
-
return { kind: "invalid", subject: "schema", reason: error.message };
|
|
344
|
-
}
|
|
345
|
-
if (error instanceof Error && /^reference "urn:said:[^"]+" resolves to more than one schema$/.test(error.message)) {
|
|
346
|
-
return { kind: "invalid", subject: "schema", reason: error.message };
|
|
347
|
-
}
|
|
348
|
-
throw error;
|
|
349
390
|
}
|
|
350
|
-
|
|
351
|
-
throw new Error("Admitted schema was not registered");
|
|
352
|
-
return validate(acdc.sad)
|
|
391
|
+
return compiled.validate(acdc.sad)
|
|
353
392
|
? { kind: "valid" }
|
|
354
|
-
: { kind: "invalid", subject: "credential", reason: validator.errorsText(validate.errors) };
|
|
393
|
+
: { kind: "invalid", subject: "credential", reason: compiled.validator.errorsText(compiled.validate.errors) };
|
|
355
394
|
}
|
|
356
395
|
catch (error) {
|
|
357
396
|
if (error instanceof InvalidSchema)
|