@mcsherrylabs/evolver-mcp 0.13.0 → 0.14.0
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/dist/application-signer.d.ts +1 -1
- package/dist/application-signer.d.ts.map +1 -1
- package/dist/attachments.d.ts +1 -1
- package/dist/attachments.d.ts.map +1 -1
- package/dist/attachments.js +2 -2
- package/dist/attachments.js.map +1 -1
- package/dist/content-file-tools.js +1 -1
- package/dist/content-file-tools.js.map +1 -1
- package/dist/content-files.d.ts +1 -1
- package/dist/content-files.d.ts.map +1 -1
- package/dist/content-files.js +1 -1
- package/dist/content-files.js.map +1 -1
- package/dist/content-ranges.d.ts +1 -1
- package/dist/content-ranges.d.ts.map +1 -1
- package/dist/group-crypto-tools.js +1 -1
- package/dist/group-crypto-tools.js.map +1 -1
- package/dist/group-crypto.d.ts +2 -2
- package/dist/group-crypto.d.ts.map +1 -1
- package/dist/group-crypto.js +1 -1
- package/dist/group-crypto.js.map +1 -1
- package/dist/inbox.d.ts +1 -1
- package/dist/inbox.d.ts.map +1 -1
- package/dist/inbox.js +1 -1
- package/dist/inbox.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/login.d.ts +1 -1
- package/dist/login.d.ts.map +1 -1
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.d.ts.map +1 -1
- package/dist/p2p.d.ts +1 -1
- package/dist/p2p.d.ts.map +1 -1
- package/dist/p2p.js +1 -1
- package/dist/p2p.js.map +1 -1
- package/dist/processor.d.ts +1 -1
- package/dist/processor.d.ts.map +1 -1
- package/dist/processor.js +1 -1
- package/dist/processor.js.map +1 -1
- package/dist/signed-http.d.ts +1 -1
- package/dist/signed-http.d.ts.map +1 -1
- package/dist/tools.d.ts +3 -3
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +10 -10
- package/dist/tools.js.map +1 -1
- package/dist/transaction-interchange-tools.js +1 -1
- package/dist/transaction-interchange-tools.js.map +1 -1
- package/dist/transaction-interchange.d.ts +2 -2
- package/dist/transaction-interchange.d.ts.map +1 -1
- package/dist/transaction-interchange.js +2 -2
- package/dist/transaction-interchange.js.map +1 -1
- package/package.json +4 -16
- package/dist/amount.d.ts +0 -23
- package/dist/amount.d.ts.map +0 -1
- package/dist/amount.js +0 -53
- package/dist/amount.js.map +0 -1
- package/dist/channel.d.ts +0 -77
- package/dist/channel.d.ts.map +0 -1
- package/dist/channel.js +0 -287
- package/dist/channel.js.map +0 -1
- package/dist/claim.d.ts +0 -48
- package/dist/claim.d.ts.map +0 -1
- package/dist/claim.js +0 -81
- package/dist/claim.js.map +0 -1
- package/dist/content.d.ts +0 -94
- package/dist/content.d.ts.map +0 -1
- package/dist/content.js +0 -215
- package/dist/content.js.map +0 -1
- package/dist/group-ownership.d.ts +0 -78
- package/dist/group-ownership.d.ts.map +0 -1
- package/dist/group-ownership.js +0 -324
- package/dist/group-ownership.js.map +0 -1
- package/dist/group.d.ts +0 -60
- package/dist/group.d.ts.map +0 -1
- package/dist/group.js +0 -204
- package/dist/group.js.map +0 -1
- package/dist/identity.d.ts +0 -87
- package/dist/identity.d.ts.map +0 -1
- package/dist/identity.js +0 -236
- package/dist/identity.js.map +0 -1
- package/dist/node-transport.d.ts +0 -14
- package/dist/node-transport.d.ts.map +0 -1
- package/dist/node-transport.js +0 -54
- package/dist/node-transport.js.map +0 -1
- package/dist/read.d.ts +0 -252
- package/dist/read.d.ts.map +0 -1
- package/dist/read.js +0 -320
- package/dist/read.js.map +0 -1
- package/dist/result.d.ts +0 -110
- package/dist/result.d.ts.map +0 -1
- package/dist/result.js +0 -97
- package/dist/result.js.map +0 -1
- package/dist/signer.d.ts +0 -37
- package/dist/signer.d.ts.map +0 -1
- package/dist/signer.js +0 -76
- package/dist/signer.js.map +0 -1
- package/dist/write.d.ts +0 -214
- package/dist/write.d.ts.map +0 -1
- package/dist/write.js +0 -399
- package/dist/write.js.map +0 -1
package/dist/content.js
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
// The content cache is a SECOND non-signing verb family, alongside `claimIdentity` (SIGN-08): it
|
|
2
|
-
// is a DIRECT node call through `NodeClient.postContent`/`getContentStream`, because the MCP holds
|
|
3
|
-
// no key for the content it publishes or fetches — content is a loopback byte store on its OWN
|
|
4
|
-
// listener (a different port from the chain API), and reaching that listener is the entire
|
|
5
|
-
// authorization. Nothing is recorded in the session stores, no `txId` is returned, and nothing
|
|
6
|
-
// reaches `evolver_submit`.
|
|
7
|
-
//
|
|
8
|
-
// The digest is NEVER trusted from the node: `sha256Hex` is computed locally over the exact
|
|
9
|
-
// ORIGINAL bytes (never a base64 transport string), so a reader verifies against what the
|
|
10
|
-
// PUBLISHER asserted, not against what the node claims (a node-trusted digest would verify the
|
|
11
|
-
// node against itself — T-5-04).
|
|
12
|
-
//
|
|
13
|
-
// A GET that returns `null` is a DEFINITIVE negative ("no reachable holder had it" — the node
|
|
14
|
-
// walked the overlay on a miss), NOT a throw: a throw is `unreachable` (transport failure), and
|
|
15
|
-
// the two are never collapsed (Pitfall 3).
|
|
16
|
-
//
|
|
17
|
-
// ⚠️ READ GRANTS (#8). `access` is REQUIRED and never inferred: "open" sends no grant, "restricted"
|
|
18
|
-
// sends `authorizedPrincipals` through the SDK's own grant header. Every malformed/blank/oversized
|
|
19
|
-
// grant list is refused BEFORE the node is asked, so an intended restriction can never become an
|
|
20
|
-
// open publish. The grant governs which authenticated remote peer NODE may read on the node's
|
|
21
|
-
// peer route — not the MCP signer, not the trusted local listener this module talks to — and it
|
|
22
|
-
// is a serving control, not encryption. Grants on identical bytes merge by union on the node, so a
|
|
23
|
-
// narrower re-publication cannot retract an earlier wider one.
|
|
24
|
-
//
|
|
25
|
-
// ⚠️ BOUNDS. Everything is inline in model context, so both directions are capped at
|
|
26
|
-
// `CONTENT_MAX_BYTES` of ORIGINAL bytes. An over-bound GET returns NO content: there is no preview
|
|
27
|
-
// and no truncated object reported as complete. Streaming/range reads are a follow-up.
|
|
28
|
-
import { ContentHttpError, isValidPrincipalName } from "@mcsherrylabs/evolver-client";
|
|
29
|
-
import { unreachableFrom } from "./result.js";
|
|
30
|
-
/** Inline bound on ORIGINAL (decoded) bytes, for both PUT and GET: 256 KiB. */
|
|
31
|
-
export const CONTENT_MAX_BYTES = 262_144;
|
|
32
|
-
/** Longest canonical base64 accepted/returned for `CONTENT_MAX_BYTES` bytes (349 528 chars). */
|
|
33
|
-
export const CONTENT_MAX_BASE64_CHARS = 4 * Math.ceil(CONTENT_MAX_BYTES / 3);
|
|
34
|
-
/** Most principal names one restricted publication may grant. */
|
|
35
|
-
export const CONTENT_MAX_PRINCIPALS = 32;
|
|
36
|
-
/** SHA-256 of the given bytes, hex-encoded. Computed locally via Web Crypto — never trusted from
|
|
37
|
-
* the node (T-5-04). */
|
|
38
|
-
export async function sha256Hex(bytes) {
|
|
39
|
-
const d = await crypto.subtle.digest("SHA-256", new Uint8Array(bytes));
|
|
40
|
-
return Array.from(new Uint8Array(d))
|
|
41
|
-
.map((b) => b.toString(16).padStart(2, "0"))
|
|
42
|
-
.join("");
|
|
43
|
-
}
|
|
44
|
-
export const OPEN_NOTE = "OPEN: any peer node that can reach this node's content peer route and knows the key may read it. " +
|
|
45
|
-
"Not encryption; there is no retraction.";
|
|
46
|
-
export const RESTRICTED_NOTE = "RESTRICTED: remote reads are served only to authenticated peer NODES matching a granted identity, " +
|
|
47
|
-
"or currently in/owning a granted group. The trusted local content listener is NOT restricted, the " +
|
|
48
|
-
"storage operator and prior recipients are not excluded, and grants on identical bytes merge by union — " +
|
|
49
|
-
"an earlier open or wider publication is not narrowed. Not encryption, not an availability guarantee.";
|
|
50
|
-
/** Canonical, padded, standard-alphabet base64 — no whitespace, no URL-safe alphabet. */
|
|
51
|
-
const BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
52
|
-
/** A lone UTF-16 surrogate cannot be UTF-8 encoded losslessly (TextEncoder writes U+FFFD). */
|
|
53
|
-
const LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
|
|
54
|
-
/** The access the caller DELIBERATELY asked for, or a refusal. Nothing is trimmed, filtered or
|
|
55
|
-
* defaulted: an entry the node would reinterpret (blank, whitespace, comma-joined) is refused,
|
|
56
|
-
* because the alternative is publishing to a set the caller did not name. */
|
|
57
|
-
export function parseAccess(access, grants) {
|
|
58
|
-
if (access === "open") {
|
|
59
|
-
if (grants !== undefined)
|
|
60
|
-
return { kind: "partial", what: "authorizedPrincipals",
|
|
61
|
-
cause: 'access "open" takes no authorizedPrincipals — use access "restricted" to grant read access' };
|
|
62
|
-
return { mode: "open" };
|
|
63
|
-
}
|
|
64
|
-
if (access !== "restricted")
|
|
65
|
-
return { kind: "partial", what: "access",
|
|
66
|
-
cause: 'say access "open" (any peer with the key may read) or "restricted" (with authorizedPrincipals) — it is never inferred' };
|
|
67
|
-
if (!Array.isArray(grants) || grants.length === 0)
|
|
68
|
-
return { kind: "partial", what: "authorizedPrincipals",
|
|
69
|
-
cause: 'access "restricted" needs a non-empty list of identity/group names — nothing was published' };
|
|
70
|
-
if (grants.length > CONTENT_MAX_PRINCIPALS)
|
|
71
|
-
return { kind: "partial", what: "authorizedPrincipals",
|
|
72
|
-
cause: `at most ${CONTENT_MAX_PRINCIPALS} principals per publication — nothing was published` };
|
|
73
|
-
const principals = [];
|
|
74
|
-
for (const g of grants) {
|
|
75
|
-
if (typeof g !== "string" || !isValidPrincipalName(g))
|
|
76
|
-
return { kind: "partial", what: "authorizedPrincipals",
|
|
77
|
-
cause: `${JSON.stringify(g).slice(0, 80)} is not a principal name (1-128 of A-Z a-z 0-9 . _ -, no spaces or commas) — nothing was published` };
|
|
78
|
-
if (!principals.includes(g))
|
|
79
|
-
principals.push(g);
|
|
80
|
-
}
|
|
81
|
-
return { mode: "restricted", principals };
|
|
82
|
-
}
|
|
83
|
-
/** The ORIGINAL bytes to publish from exactly one of `text` / `base64`, or a refusal. */
|
|
84
|
-
function parsePayload(text, base64) {
|
|
85
|
-
if ((text === undefined) === (base64 === undefined))
|
|
86
|
-
return { kind: "partial", what: "content", cause: "give exactly one of `text` (UTF-8) or `base64` (binary)" };
|
|
87
|
-
if (text !== undefined) {
|
|
88
|
-
if (typeof text !== "string" || text.trim().length === 0)
|
|
89
|
-
return { kind: "partial", what: "text", cause: "publishing empty content is refused — write something first" };
|
|
90
|
-
if (LONE_SURROGATE.test(text))
|
|
91
|
-
return { kind: "partial", what: "text", cause: "text contains an unpaired surrogate that UTF-8 cannot carry losslessly — use `base64`" };
|
|
92
|
-
const bytes = new TextEncoder().encode(text);
|
|
93
|
-
if (bytes.length > CONTENT_MAX_BYTES)
|
|
94
|
-
return { kind: "partial", what: "text", cause: `${bytes.length} UTF-8 bytes exceeds the ${CONTENT_MAX_BYTES}-byte inline bound` };
|
|
95
|
-
return { bytes, encoding: "text" };
|
|
96
|
-
}
|
|
97
|
-
if (typeof base64 !== "string" || base64.length === 0)
|
|
98
|
-
return { kind: "partial", what: "base64", cause: "publishing empty content is refused — give non-empty base64" };
|
|
99
|
-
if (base64.length > CONTENT_MAX_BASE64_CHARS)
|
|
100
|
-
return { kind: "partial", what: "base64",
|
|
101
|
-
cause: `${base64.length} base64 characters exceeds the ${CONTENT_MAX_BASE64_CHARS}-character bound (${CONTENT_MAX_BYTES} bytes)` };
|
|
102
|
-
const buf = BASE64.test(base64) ? Buffer.from(base64, "base64") : undefined;
|
|
103
|
-
// Round-trip equality rejects non-zero pad bits, so exactly one string names each byte sequence.
|
|
104
|
-
if (buf === undefined || buf.toString("base64") !== base64)
|
|
105
|
-
return { kind: "partial", what: "base64",
|
|
106
|
-
cause: "not canonical standard base64 (A-Z a-z 0-9 + /, `=` padding, no whitespace or URL-safe alphabet)" };
|
|
107
|
-
if (buf.length > CONTENT_MAX_BYTES)
|
|
108
|
-
return { kind: "partial", what: "base64", cause: `${buf.length} bytes exceeds the ${CONTENT_MAX_BYTES}-byte inline bound` };
|
|
109
|
-
return { bytes: new Uint8Array(buf), encoding: "base64" };
|
|
110
|
-
}
|
|
111
|
-
/** Publish text or binary to the content cache (READ-11, #8). `access` must be stated; a
|
|
112
|
-
* restricted publication forwards its grants through the SDK's `authorizedPrincipals`. Every
|
|
113
|
-
* refusal happens BEFORE any call. A node answer that is not 201 (e.g. HTTP 400 for an unknown
|
|
114
|
-
* principal) is `partial publish` naming the node's answer — never `ok`, and never retried open;
|
|
115
|
-
* a transport throw is `unreachable`. */
|
|
116
|
-
export async function publishBytes(deps, req) {
|
|
117
|
-
const access = parseAccess(req.access, req.authorizedPrincipals);
|
|
118
|
-
if ("kind" in access)
|
|
119
|
-
return access;
|
|
120
|
-
const payload = parsePayload(req.text, req.base64);
|
|
121
|
-
if ("kind" in payload)
|
|
122
|
-
return payload;
|
|
123
|
-
const { bytes, encoding } = payload;
|
|
124
|
-
let r;
|
|
125
|
-
let sha256;
|
|
126
|
-
try {
|
|
127
|
-
sha256 = await sha256Hex(bytes);
|
|
128
|
-
r = access.mode === "restricted"
|
|
129
|
-
? await deps.client.postContent(bytes, { authorizedPrincipals: access.principals })
|
|
130
|
-
: await deps.client.postContent(bytes);
|
|
131
|
-
}
|
|
132
|
-
catch (e) {
|
|
133
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
134
|
-
const status = e instanceof ContentHttpError ? String(e.status) : /^postContent(?:Stream)?: HTTP (\d{3})\b/.exec(message)?.[1];
|
|
135
|
-
if (status === undefined)
|
|
136
|
-
return unreachableFrom(e, deps.contentUrl);
|
|
137
|
-
return {
|
|
138
|
-
kind: "partial",
|
|
139
|
-
what: "publish",
|
|
140
|
-
cause: status.startsWith("4")
|
|
141
|
-
? `the content node refused it (${message}) — nothing was stored; an unknown principal name is answered HTTP 400. Not retried, never re-sent open.`
|
|
142
|
-
: `the content node answered ${message} — NOT a successful publication, and storage is unconfirmed. Not retried.`,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
if (r.size !== bytes.length)
|
|
146
|
-
return { kind: "partial", what: "publish",
|
|
147
|
-
cause: `the node reported ${r.size} bytes for key ${r.key} but ${bytes.length} were sent — treat this publication as unverified` };
|
|
148
|
-
return {
|
|
149
|
-
kind: "ok",
|
|
150
|
-
value: access.mode === "restricted"
|
|
151
|
-
? { key: r.key, size: bytes.length, sha256, encoding, access: "restricted", authorizedPrincipals: access.principals, note: RESTRICTED_NOTE }
|
|
152
|
-
: { key: r.key, size: bytes.length, sha256, encoding, access: "open", note: OPEN_NOTE },
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
/** Fetch content from the cache by key (READ-10, #8), returning the COMPLETE object as strict
|
|
156
|
-
* UTF-8 `text` (default) or `base64` of the actual bytes. Empty/blank key or unknown encoding is
|
|
157
|
-
* refused locally BEFORE any call. `null` is the DEFINITIVE negative (Pitfall 3); a throw or a
|
|
158
|
-
* short/overlong transfer is `unreachable`. Over `CONTENT_MAX_BYTES`, or not valid UTF-8 in text
|
|
159
|
-
* mode, answers `partial` with NO content — never a preview, never a lossy decode. */
|
|
160
|
-
export async function fetchBytes(deps, key, encoding = "text") {
|
|
161
|
-
const k = String(key ?? "").trim();
|
|
162
|
-
if (k.length === 0)
|
|
163
|
-
return { kind: "partial", what: "key", cause: "which content? a non-empty key is required" };
|
|
164
|
-
if (encoding !== "text" && encoding !== "base64")
|
|
165
|
-
return { kind: "partial", what: "encoding", cause: 'encoding is "text" (strict UTF-8) or "base64"' };
|
|
166
|
-
let bytes;
|
|
167
|
-
try {
|
|
168
|
-
const got = await deps.client.getContentStream(k);
|
|
169
|
-
if (got === null)
|
|
170
|
-
return { kind: "ok", value: null };
|
|
171
|
-
if (got.size > CONTENT_MAX_BYTES) {
|
|
172
|
-
await got.stream.cancel().catch(() => undefined);
|
|
173
|
-
return { kind: "partial", what: "content",
|
|
174
|
-
cause: `the object is ${got.size} bytes, over the ${CONTENT_MAX_BYTES}-byte inline bound — nothing returned (no preview); streaming/range reads are not exposed` };
|
|
175
|
-
}
|
|
176
|
-
const reader = got.stream.getReader();
|
|
177
|
-
const parts = [];
|
|
178
|
-
let total = 0;
|
|
179
|
-
for (;;) {
|
|
180
|
-
const { done, value } = await reader.read();
|
|
181
|
-
if (done)
|
|
182
|
-
break;
|
|
183
|
-
total += value.length;
|
|
184
|
-
if (total > got.size) {
|
|
185
|
-
await reader.cancel().catch(() => undefined);
|
|
186
|
-
throw new Error(`content transfer overran its declared ${got.size} bytes — discarded`);
|
|
187
|
-
}
|
|
188
|
-
parts.push(value);
|
|
189
|
-
}
|
|
190
|
-
// The SDK errors the stream on a short read, so reaching here means exactly `got.size` bytes.
|
|
191
|
-
bytes = new Uint8Array(total);
|
|
192
|
-
let off = 0;
|
|
193
|
-
for (const p of parts) {
|
|
194
|
-
bytes.set(p, off);
|
|
195
|
-
off += p.length;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
catch (e) {
|
|
199
|
-
return unreachableFrom(e, deps.contentUrl);
|
|
200
|
-
}
|
|
201
|
-
const sha256 = await sha256Hex(bytes);
|
|
202
|
-
if (encoding === "base64")
|
|
203
|
-
return { kind: "ok", value: { key: k, size: bytes.length, sha256, complete: true, encoding: "base64", base64: Buffer.from(bytes).toString("base64") } };
|
|
204
|
-
let text;
|
|
205
|
-
try {
|
|
206
|
-
// fatal: refuse rather than substitute U+FFFD; ignoreBOM: keep a leading BOM as content.
|
|
207
|
-
text = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
208
|
-
}
|
|
209
|
-
catch {
|
|
210
|
-
return { kind: "partial", what: "text",
|
|
211
|
-
cause: `the object (${bytes.length} bytes, sha256 ${sha256}) is not valid UTF-8 — nothing lossy is returned; fetch with encoding "base64"` };
|
|
212
|
-
}
|
|
213
|
-
return { kind: "ok", value: { key: k, size: bytes.length, sha256, complete: true, encoding: "text", text } };
|
|
214
|
-
}
|
|
215
|
-
//# sourceMappingURL=content.js.map
|
package/dist/content.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"content.js","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import type { Signer } from "@mcsherrylabs/evolver-client";
|
|
2
|
-
import type { ChainReads } from "./read.js";
|
|
3
|
-
import type { ToolResult } from "./result.js";
|
|
4
|
-
import type { SignedGroup } from "./write.js";
|
|
5
|
-
export interface OwnershipDeps {
|
|
6
|
-
readonly identity: string;
|
|
7
|
-
readonly signer: Signer;
|
|
8
|
-
readonly reads: ChainReads;
|
|
9
|
-
}
|
|
10
|
-
/** Rounds of life for a two-party transfer. Longer than `DEFAULT_TTL_OFFSET` (50, sized for no
|
|
11
|
-
* human wait) because a second party must act, but never beyond the node's default
|
|
12
|
-
* `max-ttl-offset` (RoutingConfig.DefaultMaxTtlOffset = 100): a longer ttl is `ttl_too_far`, and
|
|
13
|
-
* a long-lived co-signed envelope is a bearer artefact. The default stays 10 rounds inside that
|
|
14
|
-
* bound so the two parties' nodes may disagree slightly about the tip. Expiry is final. */
|
|
15
|
-
export declare const OWNERSHIP_TTL_MAX = 100;
|
|
16
|
-
export declare const OWNERSHIP_TTL_DEFAULT = 90;
|
|
17
|
-
/** The one key type whose consent can be verified with a published SDK primitive. */
|
|
18
|
-
export declare const CONSENT_KEY_TYPE = "Ed25519";
|
|
19
|
-
export declare const INTENT_KIND = "evolver.group_ownership_intent.v1";
|
|
20
|
-
export declare const CONSENT_KIND = "evolver.group_ownership_consent.v1";
|
|
21
|
-
declare const PURPOSE = "AUTHORIZE";
|
|
22
|
-
export interface OwnershipIntent {
|
|
23
|
-
readonly kind: typeof INTENT_KIND;
|
|
24
|
-
readonly txId: string;
|
|
25
|
-
readonly ledgerId: string;
|
|
26
|
-
readonly groupName: string;
|
|
27
|
-
readonly outgoingOwner: string;
|
|
28
|
-
readonly incomingOwner: string;
|
|
29
|
-
readonly expiresAtRound: number;
|
|
30
|
-
readonly preparedAtRound: number;
|
|
31
|
-
readonly maxToll: "0";
|
|
32
|
-
readonly next: string;
|
|
33
|
-
}
|
|
34
|
-
export interface OwnershipConsent {
|
|
35
|
-
readonly kind: typeof CONSENT_KIND;
|
|
36
|
-
readonly txId: string;
|
|
37
|
-
readonly ledgerId: string;
|
|
38
|
-
readonly groupName: string;
|
|
39
|
-
readonly outgoingOwner: string;
|
|
40
|
-
readonly incomingOwner: string;
|
|
41
|
-
readonly expiresAtRound: number;
|
|
42
|
-
readonly maxToll: "0";
|
|
43
|
-
readonly auth: {
|
|
44
|
-
readonly nodeId: string;
|
|
45
|
-
readonly keyType: string;
|
|
46
|
-
readonly keyLabel: string;
|
|
47
|
-
readonly purpose: typeof PURPOSE;
|
|
48
|
-
readonly signatureHex: string;
|
|
49
|
-
};
|
|
50
|
-
readonly next: string;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* PREPARE (outgoing owner). Signs nothing; returns the public intent for the incoming owner.
|
|
54
|
-
*/
|
|
55
|
-
export declare function prepareOwnershipTransfer(deps: OwnershipDeps, params: {
|
|
56
|
-
groupName: unknown;
|
|
57
|
-
incomingOwner: unknown;
|
|
58
|
-
ttlRounds?: unknown;
|
|
59
|
-
}): Promise<ToolResult<OwnershipIntent>>;
|
|
60
|
-
/**
|
|
61
|
-
* CONSENT (incoming owner). Recomputes the intent's txId and signs it with THIS session's key.
|
|
62
|
-
*/
|
|
63
|
-
export declare function consentOwnershipTransfer(deps: OwnershipDeps, params: {
|
|
64
|
-
txId: unknown;
|
|
65
|
-
groupName: unknown;
|
|
66
|
-
outgoingOwner: unknown;
|
|
67
|
-
incomingOwner: unknown;
|
|
68
|
-
expiresAtRound: unknown;
|
|
69
|
-
}): Promise<ToolResult<OwnershipConsent>>;
|
|
70
|
-
/**
|
|
71
|
-
* FINALIZE (outgoing owner). Verifies the consent, signs as sender, assembles both auths, records.
|
|
72
|
-
*/
|
|
73
|
-
export declare function finalizeOwnershipTransfer(deps: OwnershipDeps, params: {
|
|
74
|
-
txId: unknown;
|
|
75
|
-
consent: unknown;
|
|
76
|
-
}): Promise<ToolResult<SignedGroup>>;
|
|
77
|
-
export {};
|
|
78
|
-
//# sourceMappingURL=group-ownership.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"group-ownership.d.ts","sourceRoot":"","sources":["../src/group-ownership.ts"],"names":[],"mappings":"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"}
|
package/dist/group-ownership.js
DELETED
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
// Group OWNERSHIP transfer (issue #9) — a two-party prepare → consent → finalize → submit workflow.
|
|
2
|
-
// NOT a credit transfer: `evolver_sign_transfer_many` moves credit; this hands a group to a new owner.
|
|
3
|
-
//
|
|
4
|
-
// The chain applies `TransferGroupOwnershipTxBody` only when the SignedTx carries an auth whose
|
|
5
|
-
// `node_id` is the incoming owner (GroupLedgerOrchestrator.applyTransferOwnership), and admission
|
|
6
|
-
// verifies EVERY auth against the named identity's REGISTERED (node_id, key_type, key_label) key
|
|
7
|
-
// over the same auth-free digest (BlockPayloadValidator.admitFilter / resolveAuths). So:
|
|
8
|
-
//
|
|
9
|
-
// 1. PREPARE (outgoing owner's session) — composes the intent with the SDK body composer and the
|
|
10
|
-
// SDK canonical digest. Signs NOTHING and records no bytes, so `evolver_submit` cannot find it.
|
|
11
|
-
// 2. CONSENT (incoming owner's session, its own host-held key) — recomputes the digest from the
|
|
12
|
-
// structured intent, refuses a mismatching txId, signs the digest with ITS signer and returns
|
|
13
|
-
// one public auth. It never holds a submittable envelope.
|
|
14
|
-
// 3. FINALIZE (outgoing owner's session) — re-reads the chain, verifies the consent signature
|
|
15
|
-
// against the incoming owner's REGISTERED key, then signs as sender and assembles BOTH auths
|
|
16
|
-
// with the SDK `encodeSignedTx`. Recorded once, strictly, by id.
|
|
17
|
-
// 4. `evolver_submit` — the ordinary path and its guards, unchanged.
|
|
18
|
-
//
|
|
19
|
-
// ⚠️ THE txId IS AUTH-FREE. SDK `txId` = node `signingMaterial` = SHA-256 over the tx-auth tag,
|
|
20
|
-
// ledger, sender, ttl, body hash and maxToll — no auths (SignedTxOps.txId, signed_tx.proto). So the
|
|
21
|
-
// prepared id, the consented id and the submitted id are one value; finalize re-derives and checks
|
|
22
|
-
// it rather than assuming.
|
|
23
|
-
//
|
|
24
|
-
// ⚠️ ONLY Ed25519 CONSENT IS ACCEPTED BY THIS WORKFLOW. Verifying before assembly is the point;
|
|
25
|
-
// a BLS consent remains refused locally, never assembled blind or verified by a hand-rolled
|
|
26
|
-
// pairing check. Published SDK 0.22.0 `edVerify` uses strict RFC 8032 verification (`zip215:false`),
|
|
27
|
-
// matching the node's strict Ed25519 mode rather than the former permissive ZIP215 behavior.
|
|
28
|
-
// This dependency update does not broaden accepted consent algorithms or remove local guards.
|
|
29
|
-
//
|
|
30
|
-
// ⚠️ KEY MATERIAL NEVER ENTERS OR LEAVES. Arguments and results carry names, the digest-bound
|
|
31
|
-
// intent, key type/label and the single consent signature; never a private key or signed envelope.
|
|
32
|
-
//
|
|
33
|
-
// ⚠️ THE CONSENT IS SAFE TO HAND OVER ONLY ON A NODE THAT REQUIRES THE SENDER'S OWN AUTH. Before
|
|
34
|
-
// the #9 node fix, admission/validation/apply accepted ANY verified auth, so the incoming owner's
|
|
35
|
-
// consent alone was a submittable transfer "from" the outgoing owner: a bearer artefact that seized
|
|
36
|
-
// the group and charged a sender who never signed. Every step therefore refuses unless the
|
|
37
|
-
// connected node's /status says `senderAuthorizationRequired: true` (SDK `nodeStatus`). That is
|
|
38
|
-
// the connected node's assertion only: the whole chain must run compatible nodes (a deployment
|
|
39
|
-
// prerequisite, not something this process can verify), and the canonical digest has no chain id,
|
|
40
|
-
// so a consent remains replayable on another chain with the same names/keys until it expires.
|
|
41
|
-
import { LEDGER, encodeSignedTx, txId as txDigest, transferGroupOwnershipBody, isValidGroupName, isValidNodeId, hexToBytes, edVerify, edSpkiToRaw, } from "@mcsherrylabs/evolver-client";
|
|
42
|
-
import { recordAssembledOnce, recordPendingOwnership, pendingOwnershipById, dropPendingOwnership, pruneExpiredPendingOwnership, recordedById, namedInRefusal, MAX_PENDING_OWNERSHIP, } from "./write.js";
|
|
43
|
-
/** Rounds of life for a two-party transfer. Longer than `DEFAULT_TTL_OFFSET` (50, sized for no
|
|
44
|
-
* human wait) because a second party must act, but never beyond the node's default
|
|
45
|
-
* `max-ttl-offset` (RoutingConfig.DefaultMaxTtlOffset = 100): a longer ttl is `ttl_too_far`, and
|
|
46
|
-
* a long-lived co-signed envelope is a bearer artefact. The default stays 10 rounds inside that
|
|
47
|
-
* bound so the two parties' nodes may disagree slightly about the tip. Expiry is final. */
|
|
48
|
-
export const OWNERSHIP_TTL_MAX = 100;
|
|
49
|
-
export const OWNERSHIP_TTL_DEFAULT = 90;
|
|
50
|
-
/** The one key type whose consent can be verified with a published SDK primitive. */
|
|
51
|
-
export const CONSENT_KEY_TYPE = "Ed25519";
|
|
52
|
-
export const INTENT_KIND = "evolver.group_ownership_intent.v1";
|
|
53
|
-
export const CONSENT_KIND = "evolver.group_ownership_consent.v1";
|
|
54
|
-
const PURPOSE = "AUTHORIZE"; // TxAuth.purpose default; encodeSignedTx writes no other.
|
|
55
|
-
const MAX_LABEL = 128;
|
|
56
|
-
const refuse = (what, cause) => ({ kind: "partial", what, cause });
|
|
57
|
-
const unreachable = (e) => ({ kind: "unreachable", chain: "", cause: e instanceof Error ? e.message : String(e) });
|
|
58
|
-
const hex = (b) => Buffer.from(b).toString("hex");
|
|
59
|
-
const str = (v) => (typeof v === "string" ? v.trim() : "");
|
|
60
|
-
/** The canonical digest (== txId) for one transfer intent, from the SDK composer and SDK digest.
|
|
61
|
-
* maxToll is 0: the groups ledger charges no toll and this workflow consents to none. */
|
|
62
|
-
function digestFor(outgoing, groupName, incoming, ttl) {
|
|
63
|
-
const body = transferGroupOwnershipBody({ groupName, newOwnerNodeId: incoming });
|
|
64
|
-
return { body, digest: txDigest(LEDGER.groups, outgoing, ttl, body, 0n) };
|
|
65
|
-
}
|
|
66
|
-
/** Refusals shared by every step, before any read: names and the outgoing/incoming distinction. */
|
|
67
|
-
function validNames(groupName, outgoing, incoming) {
|
|
68
|
-
if (!isValidGroupName(groupName))
|
|
69
|
-
return refuse("groupName", "not a valid group name");
|
|
70
|
-
if (!isValidNodeId(outgoing))
|
|
71
|
-
return refuse("outgoingOwner", "not a valid identity id");
|
|
72
|
-
if (!isValidNodeId(incoming))
|
|
73
|
-
return refuse("incomingOwner", "not a valid identity id");
|
|
74
|
-
if (outgoing === incoming)
|
|
75
|
-
return refuse("incomingOwner", "the incoming owner equals the outgoing owner — the chain fines that rather than refusing it");
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
/** Chain facts the chain will itself enforce at apply (a mismatch is a FINE, too late to refuse):
|
|
79
|
-
* the group's committed owner, and that the incoming owner is an existing identity, not a group. */
|
|
80
|
-
async function checkChainFacts(reads, groupName, outgoing, incoming) {
|
|
81
|
-
try {
|
|
82
|
-
const listed = (await reads.listGroups()).find((g) => g.ledgerId === groupName);
|
|
83
|
-
if (listed === undefined)
|
|
84
|
-
return { kind: "not_found", what: `group "${groupName}"` };
|
|
85
|
-
if (listed.owner !== outgoing)
|
|
86
|
-
return refuse("outgoingOwner", `group "${groupName}" is owned by "${listed.owner}" at the node's applied state, not "${outgoing}"`);
|
|
87
|
-
if ((await reads.getGroup(incoming)) !== null)
|
|
88
|
-
return refuse("incomingOwner", `"${incoming}" is a group; only an identity can own a group`);
|
|
89
|
-
if ((await reads.getIdentity(incoming)) === null)
|
|
90
|
-
return { kind: "not_found", what: `identity "${incoming}" (incoming owner)` };
|
|
91
|
-
}
|
|
92
|
-
catch (e) {
|
|
93
|
-
return unreachable(e);
|
|
94
|
-
}
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
/** Freshness under the chain's own admit rule: `tip < ttl <= tip + max-ttl-offset`. */
|
|
98
|
-
function checkFresh(tip, expiresAtRound) {
|
|
99
|
-
if (expiresAtRound <= tip)
|
|
100
|
-
return refuse("expiresAtRound", `expired: round ${expiresAtRound} is not after the current round ${tip}. Expiry is final; prepare a NEW transfer (a new txId) rather than reusing this one`);
|
|
101
|
-
if (expiresAtRound - tip > OWNERSHIP_TTL_MAX)
|
|
102
|
-
return refuse("expiresAtRound", `round ${expiresAtRound} is more than ${OWNERSHIP_TTL_MAX} rounds after the current round ${tip}; refused as over-long`);
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
/** The incoming owner's REGISTERED key for (identity, Ed25519, label), raw 32 bytes. Knowing a
|
|
106
|
-
* public key is not authority; only the chain's registration of it for this identity is. */
|
|
107
|
-
async function registeredEdKey(reads, who, keyLabel) {
|
|
108
|
-
let rows;
|
|
109
|
-
try {
|
|
110
|
-
rows = await reads.getIdentityKeys(who);
|
|
111
|
-
}
|
|
112
|
-
catch (e) {
|
|
113
|
-
return unreachable(e);
|
|
114
|
-
}
|
|
115
|
-
if (rows === null)
|
|
116
|
-
return { kind: "not_found", what: `identity "${who}"` };
|
|
117
|
-
const match = rows.filter((r) => r.keyType === CONSENT_KEY_TYPE && r.label === keyLabel);
|
|
118
|
-
if (match.length !== 1)
|
|
119
|
-
return refuse("consent.auth", `"${who}" has no single registered ${CONSENT_KEY_TYPE} key labelled "${keyLabel}" at the node's applied state (unknown_key on chain)`);
|
|
120
|
-
try {
|
|
121
|
-
return edSpkiToRaw(hexToBytes(match[0].publicKeyHex));
|
|
122
|
-
}
|
|
123
|
-
catch {
|
|
124
|
-
return refuse("consent.auth", `the registered ${CONSENT_KEY_TYPE} key for "${who}" is not a recognisable Ed25519 public key`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
/** Fail closed unless the CONNECTED node states it requires the sender's own AUTHORIZE auth.
|
|
128
|
-
* Absent/null/false/non-boolean is unsupported (`partial`); a failed read is `unreachable`. */
|
|
129
|
-
async function requireSenderAuthorizationNode(reads) {
|
|
130
|
-
let flag;
|
|
131
|
-
try {
|
|
132
|
-
flag = (await reads.nodeStatus()).senderAuthorizationRequired;
|
|
133
|
-
}
|
|
134
|
-
catch (e) {
|
|
135
|
-
return unreachable(e);
|
|
136
|
-
}
|
|
137
|
-
if (flag === true)
|
|
138
|
-
return null;
|
|
139
|
-
return refuse("node", "the connected node does not advertise senderAuthorizationRequired: true. On such a node the " +
|
|
140
|
-
"incoming owner's consent alone could be submitted as a transfer the outgoing owner never signed, " +
|
|
141
|
-
"so nothing is prepared, signed or returned. Use a node (and a chain) running the sender-authorization fix.");
|
|
142
|
-
}
|
|
143
|
-
async function tip(reads) {
|
|
144
|
-
try {
|
|
145
|
-
return await reads.currentRound();
|
|
146
|
-
}
|
|
147
|
-
catch (e) {
|
|
148
|
-
return unreachable(e);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* PREPARE (outgoing owner). Signs nothing; returns the public intent for the incoming owner.
|
|
153
|
-
*/
|
|
154
|
-
export async function prepareOwnershipTransfer(deps, params) {
|
|
155
|
-
const groupName = str(params.groupName), incoming = str(params.incomingOwner), outgoing = deps.identity;
|
|
156
|
-
const bad = validNames(groupName, outgoing, incoming);
|
|
157
|
-
if (bad)
|
|
158
|
-
return bad;
|
|
159
|
-
const ttlRounds = params.ttlRounds === undefined ? OWNERSHIP_TTL_DEFAULT : params.ttlRounds;
|
|
160
|
-
if (typeof ttlRounds !== "number" || !Number.isSafeInteger(ttlRounds) || ttlRounds < 1 || ttlRounds > OWNERSHIP_TTL_MAX)
|
|
161
|
-
return refuse("ttlRounds", `must be an integer 1..${OWNERSHIP_TTL_MAX} (the node's default max-ttl-offset)`);
|
|
162
|
-
const policy = await requireSenderAuthorizationNode(deps.reads);
|
|
163
|
-
if (policy)
|
|
164
|
-
return policy;
|
|
165
|
-
const facts = await checkChainFacts(deps.reads, groupName, outgoing, incoming);
|
|
166
|
-
if (facts)
|
|
167
|
-
return facts;
|
|
168
|
-
const now = await tip(deps.reads);
|
|
169
|
-
if (typeof now !== "number")
|
|
170
|
-
return now;
|
|
171
|
-
const expiresAtRound = now + ttlRounds;
|
|
172
|
-
const id = hex(digestFor(outgoing, groupName, incoming, expiresAtRound).digest);
|
|
173
|
-
const fields = { ledgerId: LEDGER.groups, groupName, outgoingOwner: outgoing, incomingOwner: incoming, maxToll: "0" };
|
|
174
|
-
const pending = {
|
|
175
|
-
txId: id, expiresAtRound, preparedAtRound: now, verb: "group_ownership_transfer_pending", fields,
|
|
176
|
-
summary: { sender: outgoing, ...fields },
|
|
177
|
-
status: "awaiting incoming-owner consent: NOT signed by this session and NOT submittable",
|
|
178
|
-
};
|
|
179
|
-
if (recordedById(id) !== undefined || pendingOwnershipById(id) !== undefined)
|
|
180
|
-
return refuse("txId", `transfer ${id} is already prepared or finalized in this session`);
|
|
181
|
-
pruneExpiredPendingOwnership(now);
|
|
182
|
-
if (!recordPendingOwnership(pending))
|
|
183
|
-
return refuse("pending", `at most ${MAX_PENDING_OWNERSHIP} prepared transfers may await consent per session`);
|
|
184
|
-
return {
|
|
185
|
-
kind: "ok",
|
|
186
|
-
value: {
|
|
187
|
-
kind: INTENT_KIND, txId: id, ...fields, maxToll: "0", expiresAtRound, preparedAtRound: now,
|
|
188
|
-
next: `Send these public fields to "${incoming}". Their own MCP session calls evolver_group_ownership_consent ` +
|
|
189
|
-
`with them; then call evolver_group_ownership_finalize here with the returned consent before round ${expiresAtRound}.`,
|
|
190
|
-
},
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* CONSENT (incoming owner). Recomputes the intent's txId and signs it with THIS session's key.
|
|
195
|
-
*/
|
|
196
|
-
export async function consentOwnershipTransfer(deps, params) {
|
|
197
|
-
const groupName = str(params.groupName), outgoing = str(params.outgoingOwner), incoming = str(params.incomingOwner);
|
|
198
|
-
const claimedId = str(params.txId).toLowerCase();
|
|
199
|
-
const expiresAtRound = params.expiresAtRound;
|
|
200
|
-
if (incoming !== deps.identity)
|
|
201
|
-
return refuse("incomingOwner", `this session signs as "${deps.identity}", not "${incoming || "(empty)"}"; only the incoming owner's own session can consent`);
|
|
202
|
-
const bad = validNames(groupName, outgoing, incoming);
|
|
203
|
-
if (bad)
|
|
204
|
-
return bad;
|
|
205
|
-
if (typeof expiresAtRound !== "number" || !Number.isSafeInteger(expiresAtRound) || expiresAtRound < 1)
|
|
206
|
-
return refuse("expiresAtRound", "must be a positive integer round");
|
|
207
|
-
if (!/^[0-9a-f]{64}$/.test(claimedId))
|
|
208
|
-
return refuse("txId", "must be the 64-character hex txId from the intent");
|
|
209
|
-
if (deps.signer.keyType !== CONSENT_KEY_TYPE)
|
|
210
|
-
return refuse("signer", `this session's key is ${deps.signer.keyType}; the outgoing owner can verify only ${CONSENT_KEY_TYPE} consent ` +
|
|
211
|
-
`(SDK 0.21.0 exports no BLS verify). Use a session configured with this identity's registered Ed25519 key (EVOLVER_KEY_TYPE=ed25519). Nothing was signed.`);
|
|
212
|
-
const { digest } = digestFor(outgoing, groupName, incoming, expiresAtRound);
|
|
213
|
-
if (hex(digest) !== claimedId)
|
|
214
|
-
return refuse("txId", `the intent's txId ${namedInRefusal(claimedId)} does not match the transfer these fields describe; nothing was signed`);
|
|
215
|
-
// Before signing: an old node would let this consent stand alone as the sender's transfer.
|
|
216
|
-
const policy = await requireSenderAuthorizationNode(deps.reads);
|
|
217
|
-
if (policy)
|
|
218
|
-
return policy;
|
|
219
|
-
const now = await tip(deps.reads);
|
|
220
|
-
if (typeof now !== "number")
|
|
221
|
-
return now;
|
|
222
|
-
const stale = checkFresh(now, expiresAtRound);
|
|
223
|
-
if (stale)
|
|
224
|
-
return stale;
|
|
225
|
-
const facts = await checkChainFacts(deps.reads, groupName, outgoing, incoming);
|
|
226
|
-
if (facts)
|
|
227
|
-
return facts;
|
|
228
|
-
// The configured key must be the one the chain will verify against: sign, then check our own
|
|
229
|
-
// signature against the registered key, so a wrong key file fails here rather than at admit.
|
|
230
|
-
const pub = await registeredEdKey(deps.reads, incoming, deps.signer.keyLabel);
|
|
231
|
-
if (!(pub instanceof Uint8Array))
|
|
232
|
-
return pub;
|
|
233
|
-
const signature = deps.signer.sign(digest);
|
|
234
|
-
if (!edVerify(digest, signature, pub))
|
|
235
|
-
return refuse("signer", `this session's Ed25519 key is not the key registered for "${incoming}"; the consent was discarded`);
|
|
236
|
-
return {
|
|
237
|
-
kind: "ok",
|
|
238
|
-
value: {
|
|
239
|
-
kind: CONSENT_KIND, txId: claimedId, ledgerId: LEDGER.groups, groupName, outgoingOwner: outgoing,
|
|
240
|
-
incomingOwner: incoming, expiresAtRound, maxToll: "0",
|
|
241
|
-
auth: { nodeId: incoming, keyType: deps.signer.keyType, keyLabel: deps.signer.keyLabel, purpose: PURPOSE, signatureHex: hex(signature) },
|
|
242
|
-
next: `Return this consent to "${outgoing}". It authorizes ONLY txId ${claimedId}, which expires at round ` +
|
|
243
|
-
`${expiresAtRound}; it does not submit anything and cannot be used for any other operation.`,
|
|
244
|
-
},
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* FINALIZE (outgoing owner). Verifies the consent, signs as sender, assembles both auths, records.
|
|
249
|
-
*/
|
|
250
|
-
export async function finalizeOwnershipTransfer(deps, params) {
|
|
251
|
-
const id = str(params.txId).toLowerCase();
|
|
252
|
-
if (recordedById(id) !== undefined)
|
|
253
|
-
return refuse("txId", `transfer ${namedInRefusal(id)} is already finalized in this session; submit or reconcile that id, never finalize again`);
|
|
254
|
-
const pending = pendingOwnershipById(id);
|
|
255
|
-
if (pending === undefined)
|
|
256
|
-
return refuse("txId", `no transfer ${namedInRefusal(id)} was prepared by this session; only a transfer prepared here can be finalized`);
|
|
257
|
-
const f = pending.fields;
|
|
258
|
-
if (f.outgoingOwner !== deps.identity)
|
|
259
|
-
return refuse("txId", "prepared for a different sender");
|
|
260
|
-
// The consent must describe exactly the prepared intent — every field, not just the id.
|
|
261
|
-
const c = (typeof params.consent === "object" && params.consent !== null ? params.consent : {});
|
|
262
|
-
const a = (typeof c["auth"] === "object" && c["auth"] !== null ? c["auth"] : {});
|
|
263
|
-
if (c["kind"] !== CONSENT_KIND)
|
|
264
|
-
return refuse("consent.kind", `must be "${CONSENT_KIND}"`);
|
|
265
|
-
// Hex case carries no meaning; compare the canonical lowercase form (the id is already lowercase).
|
|
266
|
-
const consentId = typeof c["txId"] === "string" ? c["txId"].trim().toLowerCase() : c["txId"];
|
|
267
|
-
const mismatch = [
|
|
268
|
-
["txId", consentId, id], ["ledgerId", c["ledgerId"], f.ledgerId], ["groupName", c["groupName"], f.groupName],
|
|
269
|
-
["outgoingOwner", c["outgoingOwner"], f.outgoingOwner], ["incomingOwner", c["incomingOwner"], f.incomingOwner],
|
|
270
|
-
["expiresAtRound", c["expiresAtRound"], pending.expiresAtRound], ["maxToll", c["maxToll"], f.maxToll],
|
|
271
|
-
["auth.nodeId", a["nodeId"], f.incomingOwner], ["auth.purpose", a["purpose"], PURPOSE],
|
|
272
|
-
].find(([, got, want]) => got !== want);
|
|
273
|
-
if (mismatch)
|
|
274
|
-
return refuse(`consent.${mismatch[0]}`, "does not match the prepared transfer (wrong intent, principal or purpose); nothing was signed");
|
|
275
|
-
const keyType = a["keyType"], keyLabel = a["keyLabel"], sigHex = a["signatureHex"];
|
|
276
|
-
if (keyType !== CONSENT_KEY_TYPE)
|
|
277
|
-
return refuse("consent.auth.keyType", `only ${CONSENT_KEY_TYPE} consent can be verified (SDK 0.21.0 exports no BLS verify); nothing was signed`);
|
|
278
|
-
if (typeof keyLabel !== "string" || keyLabel.length > MAX_LABEL)
|
|
279
|
-
return refuse("consent.auth.keyLabel", "must be a string of at most 128 characters");
|
|
280
|
-
if (typeof sigHex !== "string" || !/^[0-9a-f]{128}$/.test(sigHex))
|
|
281
|
-
return refuse("consent.auth.signatureHex", "must be a 64-byte Ed25519 signature as lowercase hex");
|
|
282
|
-
// Re-derive the digest from the prepared fields with the SDK: the id must still be the intent.
|
|
283
|
-
const { body, digest } = digestFor(f.outgoingOwner, f.groupName, f.incomingOwner, pending.expiresAtRound);
|
|
284
|
-
if (hex(digest) !== id)
|
|
285
|
-
return refuse("txId", "the prepared fields no longer derive this txId; refused");
|
|
286
|
-
// Rechecked at finalization: the session may now be pointed at (or answered by) an older node.
|
|
287
|
-
const policy = await requireSenderAuthorizationNode(deps.reads);
|
|
288
|
-
if (policy)
|
|
289
|
-
return policy;
|
|
290
|
-
const now = await tip(deps.reads);
|
|
291
|
-
if (typeof now !== "number")
|
|
292
|
-
return now;
|
|
293
|
-
const stale = checkFresh(now, pending.expiresAtRound);
|
|
294
|
-
if (stale)
|
|
295
|
-
return stale;
|
|
296
|
-
const facts = await checkChainFacts(deps.reads, f.groupName, f.outgoingOwner, f.incomingOwner);
|
|
297
|
-
if (facts)
|
|
298
|
-
return facts;
|
|
299
|
-
const pub = await registeredEdKey(deps.reads, f.incomingOwner, keyLabel);
|
|
300
|
-
if (!(pub instanceof Uint8Array))
|
|
301
|
-
return pub;
|
|
302
|
-
if (!edVerify(digest, hexToBytes(sigHex), pub))
|
|
303
|
-
return refuse("consent.auth.signatureHex", `does not verify for txId ${id} under "${f.incomingOwner}"'s registered ${CONSENT_KEY_TYPE} key; nothing was signed`);
|
|
304
|
-
// Verified. Sign as sender (empty node_id ⇒ the sender) and assemble with the SDK encoder.
|
|
305
|
-
const auths = [
|
|
306
|
-
{ keyType: deps.signer.keyType, keyLabel: deps.signer.keyLabel, nodeId: "", signature: deps.signer.sign(digest) },
|
|
307
|
-
{ keyType: CONSENT_KEY_TYPE, keyLabel, nodeId: f.incomingOwner, signature: hexToBytes(sigHex) },
|
|
308
|
-
];
|
|
309
|
-
const bytes = encodeSignedTx({ ledgerId: LEDGER.groups, senderNodeId: f.outgoingOwner, body, ttlRound: pending.expiresAtRound, auths, maxToll: 0n });
|
|
310
|
-
const fields = {
|
|
311
|
-
groupName: f.groupName, outgoingOwner: f.outgoingOwner, incomingOwner: f.incomingOwner, maxToll: "0",
|
|
312
|
-
outgoingAuth: `${f.outgoingOwner} ${deps.signer.keyType} label="${deps.signer.keyLabel}"`,
|
|
313
|
-
incomingAuth: `${f.incomingOwner} ${CONSENT_KEY_TYPE} label="${keyLabel}" (verified against registered key at round ${now})`,
|
|
314
|
-
};
|
|
315
|
-
const signed = {
|
|
316
|
-
txId: id, expiresAtRound: pending.expiresAtRound, signedAtRound: now, verb: "group_ownership_transfer",
|
|
317
|
-
fields, summary: { sender: f.outgoingOwner, ...fields },
|
|
318
|
-
};
|
|
319
|
-
if (!recordAssembledOnce(signed, bytes))
|
|
320
|
-
return refuse("txId", `transfer ${id} is already recorded in this session; nothing was replaced`);
|
|
321
|
-
dropPendingOwnership(id);
|
|
322
|
-
return { kind: "ok", value: signed };
|
|
323
|
-
}
|
|
324
|
-
//# sourceMappingURL=group-ownership.js.map
|