keri-ts 0.8.0 → 0.9.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 +10 -0
- package/esm/cesr/src/primitives/counter.js +64 -9
- package/esm/cesr/src/version.js +2 -2
- package/esm/keri/src/app/cesr-http.js +4 -3
- package/esm/keri/src/app/cli/delegate.js +106 -10
- package/esm/keri/src/app/cli/did.js +182 -0
- package/esm/keri/src/app/cli/ends.js +40 -0
- package/esm/keri/src/app/cli/index.js +2 -0
- package/esm/keri/src/app/cli/ipex.js +365 -22
- package/esm/keri/src/app/cli/multisig.js +1089 -0
- package/esm/keri/src/app/cli/vc.js +259 -2
- package/esm/keri/src/app/delegating.js +26 -11
- package/esm/keri/src/app/endpoint-roleing.js +140 -0
- package/esm/keri/src/app/exchanging.js +14 -15
- package/esm/keri/src/app/forwarding.js +90 -36
- package/esm/keri/src/app/habbing.js +189 -36
- package/esm/keri/src/app/ipex-credentialing.js +41 -23
- package/esm/keri/src/app/oobiery.js +13 -2
- package/esm/keri/src/app/version.js +2 -2
- package/esm/keri/src/app/witnessing.js +82 -49
- package/esm/keri/src/core/attachment-countering.js +105 -0
- package/esm/keri/src/core/protocol-exchanging.js +13 -11
- package/esm/keri/src/core/protocol-serialization.js +102 -76
- package/esm/keri/src/db/basing.js +32 -43
- package/esm/keri/src/db/reger.js +18 -24
- package/esm/keri/src/did/index.js +7 -0
- package/esm/keri/src/did/keri/resolving.js +26 -0
- package/esm/keri/src/did/webs/artifacts.js +104 -0
- package/esm/keri/src/did/webs/designated-aliases-public-schema.js +156 -0
- package/esm/keri/src/did/webs/designated-aliases.js +235 -0
- package/esm/keri/src/did/webs/dids.js +186 -0
- package/esm/keri/src/did/webs/documenting.js +190 -0
- package/esm/keri/src/did/webs/resolving.js +115 -0
- package/esm/keri/src/runtime/index.js +2 -0
- package/esm/keri/src/vdr/credentialing.js +9 -8
- package/package.json +2 -2
- package/types/cesr/src/primitives/counter.d.ts +8 -0
- package/types/cesr/src/primitives/counter.d.ts.map +1 -1
- package/types/cesr/src/version.d.ts +2 -2
- package/types/keri/src/app/cesr-http.d.ts +2 -1
- package/types/keri/src/app/cesr-http.d.ts.map +1 -1
- package/types/keri/src/app/cli/delegate.d.ts.map +1 -1
- package/types/keri/src/app/cli/did.d.ts +10 -0
- package/types/keri/src/app/cli/did.d.ts.map +1 -0
- package/types/keri/src/app/cli/ends.d.ts.map +1 -1
- package/types/keri/src/app/cli/index.d.ts +2 -0
- package/types/keri/src/app/cli/index.d.ts.map +1 -1
- package/types/keri/src/app/cli/ipex.d.ts +9 -0
- package/types/keri/src/app/cli/ipex.d.ts.map +1 -1
- package/types/keri/src/app/cli/multisig.d.ts +12 -0
- package/types/keri/src/app/cli/multisig.d.ts.map +1 -0
- package/types/keri/src/app/cli/vc.d.ts +9 -0
- package/types/keri/src/app/cli/vc.d.ts.map +1 -1
- package/types/keri/src/app/delegating.d.ts +1 -0
- package/types/keri/src/app/delegating.d.ts.map +1 -1
- package/types/keri/src/app/endpoint-roleing.d.ts +46 -0
- package/types/keri/src/app/endpoint-roleing.d.ts.map +1 -0
- package/types/keri/src/app/exchanging.d.ts.map +1 -1
- package/types/keri/src/app/forwarding.d.ts +7 -1
- package/types/keri/src/app/forwarding.d.ts.map +1 -1
- package/types/keri/src/app/habbing.d.ts +51 -4
- package/types/keri/src/app/habbing.d.ts.map +1 -1
- package/types/keri/src/app/ipex-credentialing.d.ts +11 -4
- package/types/keri/src/app/ipex-credentialing.d.ts.map +1 -1
- package/types/keri/src/app/oobiery.d.ts +1 -0
- package/types/keri/src/app/oobiery.d.ts.map +1 -1
- package/types/keri/src/app/protocol-host-policy.d.ts +8 -0
- package/types/keri/src/app/protocol-host-policy.d.ts.map +1 -1
- package/types/keri/src/app/version.d.ts +2 -2
- package/types/keri/src/app/witnessing.d.ts.map +1 -1
- package/types/keri/src/core/attachment-countering.d.ts +39 -0
- package/types/keri/src/core/attachment-countering.d.ts.map +1 -0
- package/types/keri/src/core/protocol-exchanging.d.ts +2 -1
- package/types/keri/src/core/protocol-exchanging.d.ts.map +1 -1
- package/types/keri/src/core/protocol-serialization.d.ts +38 -4
- package/types/keri/src/core/protocol-serialization.d.ts.map +1 -1
- package/types/keri/src/db/basing.d.ts.map +1 -1
- package/types/keri/src/db/reger.d.ts +2 -2
- package/types/keri/src/db/reger.d.ts.map +1 -1
- package/types/keri/src/did/index.d.ts +8 -0
- package/types/keri/src/did/index.d.ts.map +1 -0
- package/types/keri/src/did/keri/resolving.d.ts +19 -0
- package/types/keri/src/did/keri/resolving.d.ts.map +1 -0
- package/types/keri/src/did/webs/artifacts.d.ts +19 -0
- package/types/keri/src/did/webs/artifacts.d.ts.map +1 -0
- package/types/keri/src/did/webs/designated-aliases-public-schema.d.ts +147 -0
- package/types/keri/src/did/webs/designated-aliases-public-schema.d.ts.map +1 -0
- package/types/keri/src/did/webs/designated-aliases.d.ts +46 -0
- package/types/keri/src/did/webs/designated-aliases.d.ts.map +1 -0
- package/types/keri/src/did/webs/dids.d.ts +65 -0
- package/types/keri/src/did/webs/dids.d.ts.map +1 -0
- package/types/keri/src/did/webs/documenting.d.ts +29 -0
- package/types/keri/src/did/webs/documenting.d.ts.map +1 -0
- package/types/keri/src/did/webs/resolving.d.ts +22 -0
- package/types/keri/src/did/webs/resolving.d.ts.map +1 -0
- package/types/keri/src/runtime/index.d.ts +2 -0
- package/types/keri/src/runtime/index.d.ts.map +1 -1
- package/types/keri/src/vdr/credentialing.d.ts +1 -1
- package/types/keri/src/vdr/credentialing.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -18,6 +18,16 @@ npm install keri-ts
|
|
|
18
18
|
Only those three entrypoints are supported public surfaces. Runnable CLI/server
|
|
19
19
|
ownership lives in the separate `tufa` package.
|
|
20
20
|
|
|
21
|
+
## Maintainer Docs
|
|
22
|
+
|
|
23
|
+
- `docs/ARCHITECTURE_MAP.md`: package and module ownership map
|
|
24
|
+
- `docs/design-docs/keri/ACDC_TEL_IPEX_VERIFIER_MAINTAINER_GUIDE.md`:
|
|
25
|
+
credential stack ownership and failure modes
|
|
26
|
+
- `docs/design-docs/keri/DELEGATION_MULTISIG_ENDPOINT_ROLES_MAINTAINER_GUIDE.md`:
|
|
27
|
+
delegation, group coordination, and endpoint-role mental model
|
|
28
|
+
- `docs/design-docs/keri/ATTACHMENT_COUNTER_GVRSN_MAINTAINER_GUIDE.md`:
|
|
29
|
+
attachment counter versioning and replay boundaries
|
|
30
|
+
|
|
21
31
|
## License
|
|
22
32
|
|
|
23
33
|
Licensed under the Apache License, Version 2.0 (`Apache-2.0`). See the top-level
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { b, b64ToInt, codeB2ToB64, codeB64ToB2, intToB64, nabSextets, sceil } from "../core/bytes.js";
|
|
1
|
+
import { b, b64ToInt, codeB2ToB64, codeB64ToB2, concatBytes, intToB64, nabSextets, sceil } from "../core/bytes.js";
|
|
2
2
|
import { DeserializeError, ShortageError, UnknownCodeError } from "../core/errors.js";
|
|
3
|
+
import { CtrDexV2 } from "../tables/counter-codex.js";
|
|
3
4
|
import { resolveCounterCodeNameTable, resolveCounterSizeTable } from "../tables/counter-version-registry.js";
|
|
4
5
|
import { COUNTER_HARDS } from "../tables/counter.tables.generated.js";
|
|
5
6
|
const COUNTER_BARDS = new Map([...COUNTER_HARDS.entries()].map(([code, hs]) => [
|
|
@@ -126,6 +127,38 @@ function encodeCounterFromFields(code, count, version) {
|
|
|
126
127
|
version,
|
|
127
128
|
};
|
|
128
129
|
}
|
|
130
|
+
function semanticNameForCode(code, version) {
|
|
131
|
+
const nameTable = resolveCounterCodeNameTable(version);
|
|
132
|
+
return nameTable[code] ?? null;
|
|
133
|
+
}
|
|
134
|
+
function codeForSemanticName(name, version) {
|
|
135
|
+
const nameTable = resolveCounterCodeNameTable(version);
|
|
136
|
+
for (const [code, candidate] of Object.entries(nameTable)) {
|
|
137
|
+
if (candidate === name) {
|
|
138
|
+
return code;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
function promoteCounterCodeForCount(code, count, version) {
|
|
144
|
+
const sizeTable = resolveCounterSizeTable(version);
|
|
145
|
+
const sizage = sizeTable.get(code);
|
|
146
|
+
if (!sizage) {
|
|
147
|
+
throw new UnknownCodeError(`Unsupported counter code for version: ${code}`);
|
|
148
|
+
}
|
|
149
|
+
if (count <= (64 ** sizage.ss) - 1) {
|
|
150
|
+
return code;
|
|
151
|
+
}
|
|
152
|
+
const name = semanticNameForCode(code, version);
|
|
153
|
+
if (!name || name.startsWith("Big")) {
|
|
154
|
+
throw new DeserializeError(`Invalid count=${count} for code=${code}`);
|
|
155
|
+
}
|
|
156
|
+
const bigCode = codeForSemanticName(`Big${name}`, version);
|
|
157
|
+
if (!bigCode) {
|
|
158
|
+
throw new DeserializeError(`Counter code=${code} has no Big${name} form.`);
|
|
159
|
+
}
|
|
160
|
+
return bigCode;
|
|
161
|
+
}
|
|
129
162
|
/** Normalize the supported constructor variants into one shared counter payload. */
|
|
130
163
|
function parseCounterInit(init) {
|
|
131
164
|
const version = init.version ?? { major: 2, minor: 0 };
|
|
@@ -152,6 +185,34 @@ function parseCounterInit(init) {
|
|
|
152
185
|
* payload groups and carry versioned code-name semantics.
|
|
153
186
|
*/
|
|
154
187
|
export class Counter {
|
|
188
|
+
static makeGVC(version) {
|
|
189
|
+
return new Counter({
|
|
190
|
+
code: CtrDexV2.KERIACDCGenusVersion,
|
|
191
|
+
countB64: `${intToB64(version.major, 1)}${intToB64(version.minor, 2)}`,
|
|
192
|
+
version,
|
|
193
|
+
}).qb64b;
|
|
194
|
+
}
|
|
195
|
+
static enclose({ qb64 = undefined, qb2 = undefined, code = "AttachmentGroup", version = { major: 2, minor: 0 }, } = {}) {
|
|
196
|
+
const actualQb64 = typeof qb64 === "string" ? b(qb64) : qb64;
|
|
197
|
+
if (actualQb64 !== undefined && actualQb64 !== null) {
|
|
198
|
+
if (actualQb64.length % 4 !== 0) {
|
|
199
|
+
throw new DeserializeError(`Bad enclosed qb64 length=${actualQb64.length}`);
|
|
200
|
+
}
|
|
201
|
+
const count = actualQb64.length / 4;
|
|
202
|
+
const counterCode = promoteCounterCodeForCount(codeForSemanticName(code, version) ?? code, count, version);
|
|
203
|
+
return concatBytes(new Counter({ code: counterCode, count, version }).qb64b, actualQb64);
|
|
204
|
+
}
|
|
205
|
+
if (qb2 !== undefined && qb2 !== null) {
|
|
206
|
+
if (qb2.length % 3 !== 0) {
|
|
207
|
+
throw new DeserializeError(`Bad enclosed qb2 length=${qb2.length}`);
|
|
208
|
+
}
|
|
209
|
+
const count = qb2.length / 3;
|
|
210
|
+
const counterCode = promoteCounterCodeForCount(codeForSemanticName(code, version) ?? code, count, version);
|
|
211
|
+
return concatBytes(new Counter({ code: counterCode, count, version }).qb2, qb2);
|
|
212
|
+
}
|
|
213
|
+
const counterCode = promoteCounterCodeForCount(codeForSemanticName(code, version) ?? code, 0, version);
|
|
214
|
+
return new Counter({ code: counterCode, count: 0, version }).qb64b;
|
|
215
|
+
}
|
|
155
216
|
constructor(init) {
|
|
156
217
|
Object.defineProperty(this, "_code", {
|
|
157
218
|
enumerable: true,
|
|
@@ -195,11 +256,7 @@ export class Counter {
|
|
|
195
256
|
writable: true,
|
|
196
257
|
value: void 0
|
|
197
258
|
});
|
|
198
|
-
const data = init instanceof Counter
|
|
199
|
-
? init.toCounterData()
|
|
200
|
-
: isCounterData(init)
|
|
201
|
-
? init
|
|
202
|
-
: parseCounterInit(init);
|
|
259
|
+
const data = init instanceof Counter ? init.toCounterData() : isCounterData(init) ? init : parseCounterInit(init);
|
|
203
260
|
this._code = data.code;
|
|
204
261
|
this._count = data.count;
|
|
205
262
|
this._fullSize = data.fullSize;
|
|
@@ -287,7 +344,5 @@ export function parseCounterFromBinary(input, version) {
|
|
|
287
344
|
}
|
|
288
345
|
/** Parse counter using domain hint (`txt` or `bny`) and versioned codex tables. */
|
|
289
346
|
export function parseCounter(input, version, cold) {
|
|
290
|
-
return cold === "bny"
|
|
291
|
-
? parseCounterFromBinary(input, version)
|
|
292
|
-
: parseCounterFromText(input, version);
|
|
347
|
+
return cold === "bny" ? parseCounterFromBinary(input, version) : parseCounterFromText(input, version);
|
|
293
348
|
}
|
package/esm/cesr/src/version.js
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* `src/version.ts` files by hand; edit this template instead.
|
|
7
7
|
*/
|
|
8
8
|
/** Package semantic version copied from the owning package manifest. */
|
|
9
|
-
export const PACKAGE_VERSION = "0.
|
|
9
|
+
export const PACKAGE_VERSION = "0.9.0";
|
|
10
10
|
/** Optional build metadata stamp injected by release/CI workflows. */
|
|
11
|
-
export const BUILD_METADATA = "build.
|
|
11
|
+
export const BUILD_METADATA = "build.15.488d4554e9b5462734e921e29385b9655b3c1e44";
|
|
12
12
|
/** User-facing version string with build metadata appended when present. */
|
|
13
13
|
export const DISPLAY_VERSION = BUILD_METADATA
|
|
14
14
|
? `${PACKAGE_VERSION}+${BUILD_METADATA}`
|
|
@@ -62,10 +62,10 @@ export function cesrBodyModeFromGlobal(value) {
|
|
|
62
62
|
* Body mode:
|
|
63
63
|
* - the full payload is sent in the HTTP body
|
|
64
64
|
*/
|
|
65
|
-
export function buildCesrRequest(message, { bodyMode = DEFAULT_CESR_BODY_MODE, destination, } = {}) {
|
|
65
|
+
export function buildCesrRequest(message, { bodyMode = DEFAULT_CESR_BODY_MODE, contentType = CESR_CONTENT_TYPE, destination, } = {}) {
|
|
66
66
|
const mode = normalizeCesrBodyMode(bodyMode);
|
|
67
67
|
const headers = {
|
|
68
|
-
"Content-Type":
|
|
68
|
+
"Content-Type": contentType,
|
|
69
69
|
};
|
|
70
70
|
if (destination) {
|
|
71
71
|
headers[CESR_DESTINATION_HEADER] = destination;
|
|
@@ -76,7 +76,8 @@ export function buildCesrRequest(message, { bodyMode = DEFAULT_CESR_BODY_MODE, d
|
|
|
76
76
|
body: arrayBufferFromBytes(message),
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
|
-
const
|
|
79
|
+
const { smellage } = smell(message);
|
|
80
|
+
const serder = parseSerder(message.slice(0, smellage.size), smellage);
|
|
80
81
|
headers[CESR_ATTACHMENT_HEADER] = textDecoder.decode(message.slice(serder.size));
|
|
81
82
|
return {
|
|
82
83
|
headers,
|
|
@@ -17,6 +17,7 @@ import { ValidationError } from "../../core/errors.js";
|
|
|
17
17
|
import { dgKey } from "../../db/core/keys.js";
|
|
18
18
|
import { makeNowIso8601 } from "../../time/mod.js";
|
|
19
19
|
import { createAgentRuntime, processRuntimeTurn, processRuntimeUntil, } from "../agent-runtime.js";
|
|
20
|
+
import { MULTISIG_IXN_ROUTE } from "../grouping.js";
|
|
20
21
|
import { queryTransportSink } from "../query-transport.js";
|
|
21
22
|
import { WitnessReceiptor } from "../witnessing.js";
|
|
22
23
|
import { setupHby } from "./common/existing.js";
|
|
@@ -86,6 +87,9 @@ function anchorData(serder) {
|
|
|
86
87
|
}
|
|
87
88
|
return { i: serder.pre, s: serder.snh, d: serder.said };
|
|
88
89
|
}
|
|
90
|
+
function approvingEvent(hby, serder, delegator) {
|
|
91
|
+
return hby.db.fetchLastSealingEventByEventSeal(delegator.pre, anchorData(serder));
|
|
92
|
+
}
|
|
89
93
|
/** Resolve delegate witnesses for either delegated inception or rotation. */
|
|
90
94
|
function delegateWitnesses(hby, serder) {
|
|
91
95
|
if ((serder.sn ?? 0) === 0) {
|
|
@@ -123,6 +127,57 @@ function delegateWitnessLogsQuery(hab, serder, witness) {
|
|
|
123
127
|
msgs: [hab.query(serder.pre, witness, query, "logs")],
|
|
124
128
|
};
|
|
125
129
|
}
|
|
130
|
+
function isGroupHab(hby, hab) {
|
|
131
|
+
return !!hab.pre && !!hby.db.getHab(hab.pre)?.mid;
|
|
132
|
+
}
|
|
133
|
+
function groupSigningMembers(hby, groupPre) {
|
|
134
|
+
const stored = hby.ks.getSmids(groupPre).map((tuple) => tuple[0].qb64);
|
|
135
|
+
if (stored.length > 0) {
|
|
136
|
+
return stored;
|
|
137
|
+
}
|
|
138
|
+
return hby.db.getHab(groupPre)?.smids ?? [];
|
|
139
|
+
}
|
|
140
|
+
function localGroupMember(hby, groupPre) {
|
|
141
|
+
const record = hby.db.getHab(groupPre);
|
|
142
|
+
const member = record?.mid ? hby.habs.get(record.mid) : null;
|
|
143
|
+
if (!member) {
|
|
144
|
+
throw new ValidationError(`Group ${groupPre} is missing local member metadata.`);
|
|
145
|
+
}
|
|
146
|
+
return member;
|
|
147
|
+
}
|
|
148
|
+
function* publishGroupDelegationApproval(runtime, hby, groupHab, message) {
|
|
149
|
+
const member = localGroupMember(hby, groupHab.pre);
|
|
150
|
+
const smids = groupSigningMembers(hby, groupHab.pre);
|
|
151
|
+
const deliveries = [];
|
|
152
|
+
for (const recipient of smids) {
|
|
153
|
+
if (recipient === member.pre || hby.habs.has(recipient)) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const result = yield* runtime.poster.sendExchange(member, {
|
|
157
|
+
recipient,
|
|
158
|
+
route: MULTISIG_IXN_ROUTE,
|
|
159
|
+
payload: { gid: groupHab.pre, smids },
|
|
160
|
+
embeds: { ixn: message },
|
|
161
|
+
topic: "multisig",
|
|
162
|
+
});
|
|
163
|
+
deliveries.push(...result.deliveries, ...result.queued);
|
|
164
|
+
}
|
|
165
|
+
return deliveries;
|
|
166
|
+
}
|
|
167
|
+
function eventAccepted(hby, serder) {
|
|
168
|
+
return !!serder.pre && serder.said !== undefined
|
|
169
|
+
&& hby.db.kels.getLast(serder.pre, serder.sn ?? 0) === serder.said;
|
|
170
|
+
}
|
|
171
|
+
function pinApprovalSeal(hby, hab, delegated, approving) {
|
|
172
|
+
if (!approving.sner || !approving.said || !delegated.pre || !delegated.said) {
|
|
173
|
+
throw new ValidationError("Approving event material is incomplete.");
|
|
174
|
+
}
|
|
175
|
+
hby.db.aess.pin(dgKey(delegated.pre, delegated.said), [
|
|
176
|
+
approving.sner,
|
|
177
|
+
new Diger({ qb64: approving.said }),
|
|
178
|
+
]);
|
|
179
|
+
hab.kvy.processEscrowDelegables();
|
|
180
|
+
}
|
|
126
181
|
/** Route query cues through query transport and other cues through Respondant. */
|
|
127
182
|
function delegateConfirmSink(runtime, hby, hab) {
|
|
128
183
|
const querySink = queryTransportSink(runtime, hby, hab);
|
|
@@ -188,6 +243,54 @@ export function* delegateConfirmCommand(args) {
|
|
|
188
243
|
const selected = confirmArgs.auto ? pending : [pending[0]];
|
|
189
244
|
for (const serder of selected) {
|
|
190
245
|
const anchor = anchorData(serder);
|
|
246
|
+
if (isGroupHab(hby, hab)) {
|
|
247
|
+
if (!interactionApproval) {
|
|
248
|
+
throw new ValidationError("Multisig delegated approval currently requires --interact.");
|
|
249
|
+
}
|
|
250
|
+
const approving = approvingEvent(hby, serder, hab);
|
|
251
|
+
if (!approving) {
|
|
252
|
+
const created = hby.interactGroupHab(confirmArgs.alias, undefined, { data: [anchor] });
|
|
253
|
+
const deliveries = yield* publishGroupDelegationApproval(runtime, hby, created.hab, created.message);
|
|
254
|
+
console.log(JSON.stringify({
|
|
255
|
+
status: eventAccepted(hby, created.serder) ? "accepted" : "multisig-pending",
|
|
256
|
+
route: MULTISIG_IXN_ROUTE,
|
|
257
|
+
group: created.hab.pre,
|
|
258
|
+
delegated: serder.pre,
|
|
259
|
+
said: serder.said,
|
|
260
|
+
anchor: created.serder.said,
|
|
261
|
+
deliveries,
|
|
262
|
+
}));
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const witnesses = delegateWitnesses(hby, serder).sort();
|
|
266
|
+
const selectedWitness = witnesses[0];
|
|
267
|
+
const member = localGroupMember(hby, hab.pre);
|
|
268
|
+
const memberSink = delegateConfirmSink(runtime, hby, member);
|
|
269
|
+
pinApprovalSeal(hby, hab, serder, approving);
|
|
270
|
+
yield* processRuntimeTurn(runtime, {
|
|
271
|
+
hab: member,
|
|
272
|
+
pollMailbox: true,
|
|
273
|
+
sink: memberSink,
|
|
274
|
+
});
|
|
275
|
+
if (!delegateCommitted(hby, serder) && selectedWitness) {
|
|
276
|
+
yield* memberSink.send(delegateWitnessLogsQuery(member, serder, selectedWitness));
|
|
277
|
+
yield* processRuntimeUntil(runtime, () => delegateCommitted(hby, serder), { hab: member, maxTurns: 128, pollMailbox: true, sink: memberSink });
|
|
278
|
+
}
|
|
279
|
+
pinApprovalSeal(hby, hab, serder, approving);
|
|
280
|
+
yield* processRuntimeTurn(runtime, {
|
|
281
|
+
hab: member,
|
|
282
|
+
pollMailbox: true,
|
|
283
|
+
sink: memberSink,
|
|
284
|
+
});
|
|
285
|
+
if (serder.pre && serder.said) {
|
|
286
|
+
const stillPending = hby.db.delegables.get([serder.pre]).includes(serder.said);
|
|
287
|
+
if (stillPending) {
|
|
288
|
+
throw new ValidationError(`Delegated event ${serder.said} remained in delegables escrow after approval.`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
console.log(`Approved delegated ${serder.ilk} ${serder.said ?? ""} for ${serder.pre ?? ""} using multisig ixn.`);
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
191
294
|
// KERIpy permits either interaction or rotation approval. Keep this
|
|
192
295
|
// explicit because the chosen approving event type affects later
|
|
193
296
|
// replay, but the embedded anchor seal is the same.
|
|
@@ -205,16 +308,9 @@ export function* delegateConfirmCommand(args) {
|
|
|
205
308
|
});
|
|
206
309
|
}
|
|
207
310
|
const approving = hby.db.getEvtSerder(hab.pre, hab.kever.said);
|
|
208
|
-
if (!approving
|
|
311
|
+
if (!approving) {
|
|
209
312
|
throw new ValidationError("Approving event material is incomplete.");
|
|
210
313
|
}
|
|
211
|
-
const pinApprovalSeal = () => {
|
|
212
|
-
hby.db.aess.pin(dgKey(serder.pre, serder.said), [
|
|
213
|
-
approving.sner,
|
|
214
|
-
new Diger({ qb64: approving.said }),
|
|
215
|
-
]);
|
|
216
|
-
hab.kvy.processEscrowDelegables();
|
|
217
|
-
};
|
|
218
314
|
const witnesses = delegateWitnesses(hby, serder).sort();
|
|
219
315
|
const selectedWitness = witnesses[0];
|
|
220
316
|
if (selectedWitness) {
|
|
@@ -229,7 +325,7 @@ export function* delegateConfirmCommand(args) {
|
|
|
229
325
|
// No delegate witness exists to query. Process the local delegable
|
|
230
326
|
// after the delegator has anchored approval; the delegate still
|
|
231
327
|
// discovers approval through its own delegator-KEL query path.
|
|
232
|
-
pinApprovalSeal();
|
|
328
|
+
pinApprovalSeal(hby, hab, serder, approving);
|
|
233
329
|
yield* processRuntimeUntil(runtime, () => querier.done, { hab, maxTurns: 128, pollMailbox: true, sink });
|
|
234
330
|
}
|
|
235
331
|
yield* processRuntimeTurn(runtime, { hab, pollMailbox: true, sink });
|
|
@@ -237,7 +333,7 @@ export function* delegateConfirmCommand(args) {
|
|
|
237
333
|
// delegated unescrow after the delegate event has been observed as
|
|
238
334
|
// locally committed through the witness-backed query/replay path.
|
|
239
335
|
if (selectedWitness) {
|
|
240
|
-
pinApprovalSeal();
|
|
336
|
+
pinApprovalSeal(hby, hab, serder, approving);
|
|
241
337
|
}
|
|
242
338
|
if (serder.pre && serder.said) {
|
|
243
339
|
const stillPending = hby.db.delegables.get([serder.pre]).includes(serder.said);
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DID-related CLI operations shared by the Tufa command dispatcher.
|
|
3
|
+
*/
|
|
4
|
+
import * as dntShim from "../../../../_dnt.shims.js";
|
|
5
|
+
import { ValidationError } from "../../core/errors.js";
|
|
6
|
+
import { Reger } from "../../db/reger.js";
|
|
7
|
+
import { bindDesignatedAliases, DEFAULT_DESIGNATED_ALIASES_REGISTRY_NAME, generateDidWebsArtifacts, parseDidWebs, resolveDidKeri, resolveDidWebs, } from "../../did/index.js";
|
|
8
|
+
import { createAgentRuntime } from "../agent-runtime.js";
|
|
9
|
+
import { WitnessReceiptor } from "../witnessing.js";
|
|
10
|
+
import { setupHby } from "./common/existing.js";
|
|
11
|
+
/** Implement `tufa dws bind`. */
|
|
12
|
+
export function* dwsBindCommand(args) {
|
|
13
|
+
const commandArgs = {
|
|
14
|
+
...baseArgs(args),
|
|
15
|
+
alias: args.alias,
|
|
16
|
+
dids: asStringList(args.did),
|
|
17
|
+
registryName: args.registryName,
|
|
18
|
+
createRegistry: args.createRegistry,
|
|
19
|
+
allowExternalDid: args.allowExternalDid,
|
|
20
|
+
};
|
|
21
|
+
requireNonEmpty(commandArgs.alias, "Alias");
|
|
22
|
+
const { hby, runtime } = yield* openRuntime(commandArgs);
|
|
23
|
+
try {
|
|
24
|
+
const hab = hby.habByName(commandArgs.alias);
|
|
25
|
+
const priorSn = hab?.kever?.sn ?? 0;
|
|
26
|
+
const result = bindDesignatedAliases(runtime, {
|
|
27
|
+
alias: commandArgs.alias,
|
|
28
|
+
dids: commandArgs.dids,
|
|
29
|
+
registryName: commandArgs.registryName ?? DEFAULT_DESIGNATED_ALIASES_REGISTRY_NAME,
|
|
30
|
+
createRegistry: commandArgs.createRegistry ?? true,
|
|
31
|
+
allowExternalDid: commandArgs.allowExternalDid ?? false,
|
|
32
|
+
});
|
|
33
|
+
if (hab?.pre) {
|
|
34
|
+
yield* receiptNewWitnessEvents(hby, hab.pre, priorSn);
|
|
35
|
+
}
|
|
36
|
+
console.log(JSON.stringify(result));
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
yield* closeRuntime(hby, runtime);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Implement `tufa dws generate`. */
|
|
43
|
+
export function* dwsGenerateCommand(args) {
|
|
44
|
+
const commandArgs = {
|
|
45
|
+
...baseArgs(args),
|
|
46
|
+
alias: args.alias,
|
|
47
|
+
did: args.did,
|
|
48
|
+
outputDir: args.outputDir,
|
|
49
|
+
meta: args.meta,
|
|
50
|
+
};
|
|
51
|
+
requireNonEmpty(commandArgs.alias, "Alias");
|
|
52
|
+
requireNonEmpty(commandArgs.did, "DID");
|
|
53
|
+
requireNonEmpty(commandArgs.outputDir, "Output directory");
|
|
54
|
+
const { hby, runtime } = yield* openRuntime(commandArgs);
|
|
55
|
+
try {
|
|
56
|
+
const artifacts = generateDidWebsArtifacts(runtime, {
|
|
57
|
+
alias: commandArgs.alias,
|
|
58
|
+
did: commandArgs.did,
|
|
59
|
+
metadata: commandArgs.meta ?? false,
|
|
60
|
+
});
|
|
61
|
+
const parsed = parseDidWebs(commandArgs.did);
|
|
62
|
+
const dir = artifactOutputDir(commandArgs.outputDir, parsed.path, artifacts.aid);
|
|
63
|
+
dntShim.Deno.mkdirSync(dir, { recursive: true });
|
|
64
|
+
dntShim.Deno.writeFileSync(`${dir}/did.json`, artifacts.didJson);
|
|
65
|
+
dntShim.Deno.writeFileSync(`${dir}/keri.cesr`, artifacts.keriCesr);
|
|
66
|
+
console.log(JSON.stringify({
|
|
67
|
+
aid: artifacts.aid,
|
|
68
|
+
did: commandArgs.did,
|
|
69
|
+
didJson: `${dir}/did.json`,
|
|
70
|
+
keriCesr: `${dir}/keri.cesr`,
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
yield* closeRuntime(hby, runtime);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** Implement `tufa dws resolve`. */
|
|
78
|
+
export function* dwsResolveCommand(args) {
|
|
79
|
+
const commandArgs = {
|
|
80
|
+
...baseArgs(args),
|
|
81
|
+
did: args.did,
|
|
82
|
+
meta: args.meta,
|
|
83
|
+
insecureHttp: args.insecureHttp,
|
|
84
|
+
};
|
|
85
|
+
requireNonEmpty(commandArgs.did, "DID");
|
|
86
|
+
const { hby, runtime } = yield* openRuntime(commandArgs);
|
|
87
|
+
try {
|
|
88
|
+
const result = yield* resolveDidWebs(runtime, {
|
|
89
|
+
did: commandArgs.did,
|
|
90
|
+
metadata: commandArgs.meta ?? false,
|
|
91
|
+
insecureHttp: commandArgs.insecureHttp ?? false,
|
|
92
|
+
});
|
|
93
|
+
console.log(JSON.stringify(commandArgs.meta ? result.resolution : result.document, null, 2));
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
yield* closeRuntime(hby, runtime);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Implement `tufa dkr resolve`. */
|
|
100
|
+
export function* dkrResolveCommand(args) {
|
|
101
|
+
const commandArgs = {
|
|
102
|
+
...baseArgs(args),
|
|
103
|
+
did: args.did,
|
|
104
|
+
oobis: asStringList(args.oobi),
|
|
105
|
+
meta: args.meta,
|
|
106
|
+
};
|
|
107
|
+
requireNonEmpty(commandArgs.did, "DID");
|
|
108
|
+
const { hby, runtime } = yield* openRuntime(commandArgs);
|
|
109
|
+
try {
|
|
110
|
+
const result = yield* resolveDidKeri(runtime, {
|
|
111
|
+
did: commandArgs.did,
|
|
112
|
+
oobis: commandArgs.oobis,
|
|
113
|
+
metadata: commandArgs.meta ?? false,
|
|
114
|
+
});
|
|
115
|
+
console.log(JSON.stringify(commandArgs.meta ? result.resolution : result.document, null, 2));
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
yield* closeRuntime(hby, runtime);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function baseArgs(args) {
|
|
122
|
+
return {
|
|
123
|
+
name: args.name,
|
|
124
|
+
base: args.base,
|
|
125
|
+
headDirPath: args.headDirPath,
|
|
126
|
+
passcode: args.passcode,
|
|
127
|
+
compat: args.compat,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function* openRuntime(args) {
|
|
131
|
+
requireNonEmpty(args.name, "Name");
|
|
132
|
+
const hby = yield* setupHby(args.name, args.base ?? "", args.passcode, false, args.headDirPath, {
|
|
133
|
+
compat: args.compat ?? false,
|
|
134
|
+
skipConfig: true,
|
|
135
|
+
});
|
|
136
|
+
const runtime = yield* createAgentRuntime(hby, { mode: "local" });
|
|
137
|
+
const reger = runtime.vdr.reger;
|
|
138
|
+
if (!(reger instanceof Reger)) {
|
|
139
|
+
throw new ValidationError("VDR runtime did not open Reger.");
|
|
140
|
+
}
|
|
141
|
+
return { hby, runtime, reger };
|
|
142
|
+
}
|
|
143
|
+
function* closeRuntime(hby, runtime) {
|
|
144
|
+
yield* runtime.close();
|
|
145
|
+
yield* hby.close();
|
|
146
|
+
}
|
|
147
|
+
function requireNonEmpty(value, label) {
|
|
148
|
+
if (!value || value.trim().length === 0) {
|
|
149
|
+
throw new ValidationError(`${label} is required.`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function* receiptNewWitnessEvents(hby, pre, priorSn) {
|
|
153
|
+
const hab = hby.habs.get(pre);
|
|
154
|
+
const latestSn = hab?.kever?.sn;
|
|
155
|
+
if (!hab?.kever || latestSn === undefined || latestSn <= priorSn || hab.kever.wits.length === 0) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const receiptor = new WitnessReceiptor(hby);
|
|
159
|
+
for (let sn = priorSn + 1; sn <= latestSn; sn += 1) {
|
|
160
|
+
yield* receiptor.submit(pre, { sn });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function asStringList(value) {
|
|
164
|
+
if (Array.isArray(value)) {
|
|
165
|
+
return value.filter((item) => typeof item === "string");
|
|
166
|
+
}
|
|
167
|
+
return typeof value === "string" ? [value] : [];
|
|
168
|
+
}
|
|
169
|
+
function artifactOutputDir(root, didPath, aid) {
|
|
170
|
+
const normalizedRoot = root.replace(/\/+$/u, "");
|
|
171
|
+
return [
|
|
172
|
+
normalizedRoot,
|
|
173
|
+
...didPath.map(safePathSegment),
|
|
174
|
+
safePathSegment(aid),
|
|
175
|
+
].filter((part) => part.length > 0).join("/");
|
|
176
|
+
}
|
|
177
|
+
function safePathSegment(segment) {
|
|
178
|
+
if (segment.includes("/") || segment === ".." || segment.includes("\0")) {
|
|
179
|
+
throw new ValidationError(`Unsafe DID path segment ${segment}.`);
|
|
180
|
+
}
|
|
181
|
+
return segment;
|
|
182
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ValidationError } from "../../core/errors.js";
|
|
2
2
|
import { isEndpointRole } from "../../core/roles.js";
|
|
3
3
|
import { createAgentRuntime, ingestKeriBytes, processRuntimeTurn } from "../agent-runtime.js";
|
|
4
|
+
import { endpointRoleAccepted, isLocalGroupHab, proposeGroupEndpointRole, } from "../endpoint-roleing.js";
|
|
4
5
|
import { setupHby } from "./common/existing.js";
|
|
5
6
|
/**
|
|
6
7
|
* Implement `tufa ends add` on top of the shared local runtime.
|
|
@@ -23,6 +24,7 @@ export function* endsAddCommand(args) {
|
|
|
23
24
|
alias: args.alias,
|
|
24
25
|
role: args.role,
|
|
25
26
|
eid: args.eid,
|
|
27
|
+
multisigMode: parseMultisigMode(args.multisigMode),
|
|
26
28
|
compat: args.compat,
|
|
27
29
|
};
|
|
28
30
|
if (!commandArgs.name) {
|
|
@@ -49,6 +51,35 @@ export function* endsAddCommand(args) {
|
|
|
49
51
|
throw new ValidationError(`No local AID found for alias ${commandArgs.alias}`);
|
|
50
52
|
}
|
|
51
53
|
const runtime = yield* createAgentRuntime(hby, { mode: "local" });
|
|
54
|
+
if (isLocalGroupHab(hby, hab)) {
|
|
55
|
+
if (!commandArgs.multisigMode) {
|
|
56
|
+
throw new ValidationError("Group endpoint role authorization requires --multisig-mode propose or --multisig-mode complete.");
|
|
57
|
+
}
|
|
58
|
+
if (commandArgs.multisigMode === "propose") {
|
|
59
|
+
const result = yield* proposeGroupEndpointRole(runtime, hab, {
|
|
60
|
+
eid: commandArgs.eid,
|
|
61
|
+
role: commandArgs.role,
|
|
62
|
+
allow: true,
|
|
63
|
+
});
|
|
64
|
+
console.log(JSON.stringify({
|
|
65
|
+
route: result.route,
|
|
66
|
+
said: result.said,
|
|
67
|
+
group: result.group,
|
|
68
|
+
accepted: result.accepted,
|
|
69
|
+
deliveries: result.deliveries,
|
|
70
|
+
attachmentBytes: result.attachmentBytes,
|
|
71
|
+
}));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (!endpointRoleAccepted(hby, hab.pre, commandArgs.role, commandArgs.eid)) {
|
|
75
|
+
throw new ValidationError(`Endpoint role ${commandArgs.role} for ${commandArgs.eid} is not yet approved for group ${hab.pre}.`);
|
|
76
|
+
}
|
|
77
|
+
console.log(`${commandArgs.role} ${commandArgs.eid}`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (commandArgs.multisigMode) {
|
|
81
|
+
throw new ValidationError("--multisig-mode is only valid for local group aliases.");
|
|
82
|
+
}
|
|
52
83
|
ingestKeriBytes(runtime, hab.makeEndRole(commandArgs.eid, commandArgs.role, true));
|
|
53
84
|
yield* processRuntimeTurn(runtime, { hab, pollMailbox: false });
|
|
54
85
|
const end = hby.db.ends.get([hab.pre, commandArgs.role, commandArgs.eid]);
|
|
@@ -61,3 +92,12 @@ export function* endsAddCommand(args) {
|
|
|
61
92
|
yield* hby.close();
|
|
62
93
|
}
|
|
63
94
|
}
|
|
95
|
+
function parseMultisigMode(value) {
|
|
96
|
+
if (value === undefined) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
if (value === "propose" || value === "complete") {
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
throw new ValidationError("--multisig-mode must be propose or complete.");
|
|
103
|
+
}
|
|
@@ -10,6 +10,7 @@ export { benchmarkCommand } from "./benchmark.js";
|
|
|
10
10
|
export { challengeGenerateCommand, challengeRespondCommand, challengeVerifyCommand } from "./challenge.js";
|
|
11
11
|
export { dumpEvts } from "./db-dump.js";
|
|
12
12
|
export { delegateConfirmCommand } from "./delegate.js";
|
|
13
|
+
export { dkrResolveCommand, dwsBindCommand, dwsGenerateCommand, dwsResolveCommand } from "./did.js";
|
|
13
14
|
export { endsAddCommand } from "./ends.js";
|
|
14
15
|
export { exchangeSendCommand } from "./exchange.js";
|
|
15
16
|
export { exportCommand } from "./export.js";
|
|
@@ -19,6 +20,7 @@ export { interactCommand } from "./interact.js";
|
|
|
19
20
|
export { ipexAdmitCommand, ipexAgreeCommand, ipexApplyCommand, ipexGrantCommand, ipexJoinCommand, ipexListCommand, ipexOfferCommand, ipexPollCommand, ipexSpurnCommand, } from "./ipex.js";
|
|
20
21
|
export { listCommand } from "./list.js";
|
|
21
22
|
export { locAddCommand } from "./loc.js";
|
|
23
|
+
export { multisigInceptCommand, multisigInteractCommand, multisigJoinCommand, multisigRotateCommand, multisigRpyCommand, } from "./multisig.js";
|
|
22
24
|
export { notificationsListCommand, notificationsMarkReadCommand, notificationsRemoveCommand } from "./notifications.js";
|
|
23
25
|
export { oobiGenerateCommand, oobiRequestCommand, oobiResolveCommand } from "./oobi.js";
|
|
24
26
|
export { queryCommand } from "./query.js";
|