@helm-protocol/ttt-mcp 0.3.3 → 0.4.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/README.md +10 -4
- package/dist/index.js +70 -14
- package/dist/pot_record_v08.js +10 -1
- package/dist/pot_record_v2.js +174 -0
- package/dist/tls_exporter.js +43 -0
- package/dist/tools.js +129 -1
- package/dist/transport_context.js +37 -0
- package/package.json +10 -7
package/README.md
CHANGED
|
@@ -55,12 +55,12 @@ Claude workflow → [context compressed] → agents call pot_query(eventId)
|
|
|
55
55
|
### Claude Code
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
claude mcp add ttt -- npx -y @helm-protocol/ttt-mcp@0.3.
|
|
58
|
+
claude mcp add ttt -- npx -y @helm-protocol/ttt-mcp@0.3.3
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
With an API key (raises the free limit to your plan's monthly quota):
|
|
62
62
|
```bash
|
|
63
|
-
claude mcp add ttt -e TTT_API_KEY=your-key -- npx -y @helm-protocol/ttt-mcp@0.3.
|
|
63
|
+
claude mcp add ttt -e TTT_API_KEY=your-key -- npx -y @helm-protocol/ttt-mcp@0.3.3
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
### Claude Desktop
|
|
@@ -72,7 +72,7 @@ Add to `claude_desktop_config.json`:
|
|
|
72
72
|
"mcpServers": {
|
|
73
73
|
"ttt": {
|
|
74
74
|
"command": "npx",
|
|
75
|
-
"args": ["-y", "@helm-protocol/ttt-mcp@0.3.
|
|
75
|
+
"args": ["-y", "@helm-protocol/ttt-mcp@0.3.3"],
|
|
76
76
|
"env": { "TTT_API_KEY": "your-key" }
|
|
77
77
|
}
|
|
78
78
|
}
|
|
@@ -81,7 +81,7 @@ Add to `claude_desktop_config.json`:
|
|
|
81
81
|
|
|
82
82
|
### Cursor
|
|
83
83
|
|
|
84
|
-
[](https://cursor.com/install-mcp?name=ttt&config=
|
|
84
|
+
[](https://cursor.com/install-mcp?name=ttt&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBoZWxtLXByb3RvY29sL3R0dC1tY3BAMC4zLjMiXX0=)
|
|
85
85
|
|
|
86
86
|
One-click install, or add the same `mcpServers` block above to `.cursor/mcp.json`.
|
|
87
87
|
|
|
@@ -168,6 +168,12 @@ Verify a draft-08 §3 record produced by `pot_generate`'s `potRecordV08` field:
|
|
|
168
168
|
| ctxId | string | No | Must match what `pot_generate` used, or verification fails |
|
|
169
169
|
| issuerPubKey | string | No | Hex-encoded 32-byte raw Ed25519 public key. Defaults to this server's own key. |
|
|
170
170
|
| content | string | No | Payload to check against the record's Payload Digest field |
|
|
171
|
+
| clientId | string | Required when `TTTPS_REQUIRE_REPLAY_LEDGER=1` | Stable caller identity for the replay key |
|
|
172
|
+
| sessionId | string | Required when `TTTPS_REQUIRE_REPLAY_LEDGER=1` | Active transport/session identifier for the replay key |
|
|
173
|
+
|
|
174
|
+
When `TTTPS_REQUIRE_V08_FRESHNESS=1`, `TTTPS_V08_MAX_SKEW_NS` MUST be set to a non-negative integer. The verifier rejects records outside `maxSkewNs + errorBoundUs`. When `TTTPS_REQUIRE_REPLAY_LEDGER=1`, a successful verification atomically claims `clientId/sessionId/nonce` in Redis with a 90-day TTL; Redis failure or a second claim is rejected. These flags are fail-closed controls.
|
|
175
|
+
|
|
176
|
+
The current MCP canonical wire profile is draft-11 PoT Record v2 (180 octets). `pot_generate_v2` and `pot_verify_v2` implement the fixed core record, including SHA-256 integrity over octets 0-79 and Ed25519 issuer authentication over octets 0-115. Draft-08 remains available through `pot_verify_v08` for legacy interoperability only. TLS binding proof is a separate 64/32-octet value; `TTTPS_V2_REQUIRE_BINDING=1` fails closed unless the server is running over direct TLS 1.3; stdio and plain HTTP have no TLS exporter. Configure `MCP_TLS_CERT_FILE` and `MCP_TLS_KEY_FILE` for the HTTPS mode. Replay claims use `(ctx_id, nonce)` with `TTTPS_REPLAY_TTL_SECONDS` (default 86400).
|
|
171
177
|
|
|
172
178
|
### pot_query
|
|
173
179
|
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,10 @@ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
|
4
4
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
5
5
|
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
6
6
|
var import_http = require("http");
|
|
7
|
+
var import_https = require("https");
|
|
8
|
+
var import_fs = require("fs");
|
|
9
|
+
var import_tls_exporter = require("./tls_exporter");
|
|
10
|
+
var import_transport_context = require("./transport_context");
|
|
7
11
|
var import_zod = require("zod");
|
|
8
12
|
var import_tools = require("./tools");
|
|
9
13
|
var import_auth = require("./auth");
|
|
@@ -102,7 +106,8 @@ function toolSuccess(result) {
|
|
|
102
106
|
}
|
|
103
107
|
function buildMcpServer() {
|
|
104
108
|
const s = new import_mcp.McpServer({ name: "ttt-mcp", version: "0.3.2" });
|
|
105
|
-
s.tool(
|
|
109
|
+
const registerTool = s.tool.bind(s);
|
|
110
|
+
registerTool(
|
|
106
111
|
"pot_generate",
|
|
107
112
|
"Generate a cryptographic Proof of Time timestamp (draft-helmprotocol-tttps, https://datatracker.ietf.org/doc/draft-helmprotocol-tttps/). For Claude Code workflows: use eventId + prevEventId to build a causal chain. For DeFi: use txHash + chainId + poolAddress. For a spec-conformant draft-08 \xA73 record binding this attestation to a specific piece of content, also supply contentDigest. One of eventId, txHash, or contentDigest is required.",
|
|
108
113
|
{
|
|
@@ -126,7 +131,7 @@ function buildMcpServer() {
|
|
|
126
131
|
}
|
|
127
132
|
}
|
|
128
133
|
);
|
|
129
|
-
|
|
134
|
+
registerTool(
|
|
130
135
|
"pot_verify",
|
|
131
136
|
"Verify a Proof of Time using its hash and integrity shards. Returns validity, mode (turbo/full), and timestamp.",
|
|
132
137
|
{
|
|
@@ -145,16 +150,58 @@ function buildMcpServer() {
|
|
|
145
150
|
}
|
|
146
151
|
}
|
|
147
152
|
);
|
|
148
|
-
|
|
153
|
+
registerTool(
|
|
154
|
+
"pot_generate_v2",
|
|
155
|
+
"Generate the draft-11 180-octet Proof-of-Time Record v2 core. TLS binding proof is computed only after a live TLS session exists.",
|
|
156
|
+
{
|
|
157
|
+
tsTaiUs: import_zod.z.string().describe("TAI timestamp in microseconds as a decimal string"),
|
|
158
|
+
dispersionUs: import_zod.z.number().int().nonnegative().describe("Uncertainty bound in microseconds"),
|
|
159
|
+
ctxId: import_zod.z.string().length(32).describe("16-octet context identifier encoded as hex"),
|
|
160
|
+
holderAuthData: import_zod.z.string().length(64).describe("32-octet holder public key or PSK digest encoded as hex"),
|
|
161
|
+
holderAuthType: import_zod.z.number().int().optional().describe("0x01 Ed25519 holder key (default) or 0x02 shared secret")
|
|
162
|
+
},
|
|
163
|
+
{ title: "Generate draft-11 180-octet PoT v2", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
164
|
+
async (args) => {
|
|
165
|
+
try {
|
|
166
|
+
return toolSuccess(await (0, import_tools.potGenerateV2)(args));
|
|
167
|
+
} catch (err) {
|
|
168
|
+
return toolError(err);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
registerTool(
|
|
173
|
+
"pot_verify_v2",
|
|
174
|
+
"Verify the draft-11 180-octet PoT Record v2 core. Binding-required mode fails closed in stdio because no TLS exporter session exists.",
|
|
175
|
+
{
|
|
176
|
+
potRecordV2: import_zod.z.string().length(360).describe("Hex-encoded 180-octet draft-11 v2 record"),
|
|
177
|
+
issuerPubKey: import_zod.z.string().length(64).optional().describe("Raw 32-octet issuer Ed25519 public key in hex"),
|
|
178
|
+
nowTaiUs: import_zod.z.string().optional().describe("Current TAI timestamp in microseconds"),
|
|
179
|
+
maxSkewUs: import_zod.z.string().optional().describe("Configured freshness allowance in microseconds"),
|
|
180
|
+
clientId: import_zod.z.string().optional(),
|
|
181
|
+
sessionId: import_zod.z.string().optional(),
|
|
182
|
+
bindingProof: import_zod.z.string().regex(/^(?:[0-9a-fA-F]{128}|[0-9a-fA-F]{64})$/).optional().describe("64-octet Ed25519 or 32-octet HMAC TLS binding proof")
|
|
183
|
+
},
|
|
184
|
+
{ title: "Verify draft-11 180-octet PoT v2", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
185
|
+
async (args) => {
|
|
186
|
+
try {
|
|
187
|
+
return toolSuccess(await (0, import_tools.potVerifyV2)(args));
|
|
188
|
+
} catch (err) {
|
|
189
|
+
return toolError(err);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
registerTool(
|
|
149
194
|
"pot_verify_v08",
|
|
150
|
-
"Verify a draft-helmprotocol-tttps-08 \xA73 Proof-of-Time record
|
|
195
|
+
"Verify and, when configured, admit a draft-helmprotocol-tttps-08 \xA73 Proof-of-Time record: recomputes the Commitment and Ed25519 signature, applies configured freshness, and atomically claims client/session/nonce in Redis before admission.",
|
|
151
196
|
{
|
|
152
197
|
potRecordV08: import_zod.z.string().describe("Hex-encoded 184 or 216-octet record from pot_generate's potRecordV08 field"),
|
|
153
198
|
ctxId: import_zod.z.string().max(255).optional().describe("Context identifier the record was generated under. Must match what pot_generate used, or verification fails."),
|
|
154
199
|
issuerPubKey: import_zod.z.string().optional().describe("Hex-encoded 32-byte raw Ed25519 issuer public key. Defaults to this server's own key."),
|
|
155
|
-
content: import_zod.z.string().optional().describe("The payload (utf8) to check against the record's Payload Digest field, if available")
|
|
200
|
+
content: import_zod.z.string().optional().describe("The payload (utf8) to check against the record's Payload Digest field, if available"),
|
|
201
|
+
clientId: import_zod.z.string().optional().describe("Stable caller identity for the durable replay ledger when TTTPS_REQUIRE_REPLAY_LEDGER=1"),
|
|
202
|
+
sessionId: import_zod.z.string().optional().describe("Active transport/session identifier for the durable replay ledger when TTTPS_REQUIRE_REPLAY_LEDGER=1")
|
|
156
203
|
},
|
|
157
|
-
{ title: "Verify draft-08 Proof-of-Time Record", readOnlyHint:
|
|
204
|
+
{ title: "Verify draft-08 Proof-of-Time Record", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
158
205
|
async (args) => {
|
|
159
206
|
try {
|
|
160
207
|
const result = await (0, import_tools.potVerifyV08)(args);
|
|
@@ -164,7 +211,7 @@ function buildMcpServer() {
|
|
|
164
211
|
}
|
|
165
212
|
}
|
|
166
213
|
);
|
|
167
|
-
|
|
214
|
+
registerTool(
|
|
168
215
|
"pot_query",
|
|
169
216
|
"Query Proof of Time records. Use eventId for exact O(1) lookup of a specific workflow step (collision probability 2^-256). Use startTime/endTime for time-range queries.",
|
|
170
217
|
{
|
|
@@ -183,7 +230,7 @@ function buildMcpServer() {
|
|
|
183
230
|
}
|
|
184
231
|
}
|
|
185
232
|
);
|
|
186
|
-
|
|
233
|
+
registerTool(
|
|
187
234
|
"pot_graph",
|
|
188
235
|
"Traverse the causal chain of workflow steps. Given an eventId, returns the full backward chain (ancestors via prevEventId) and forward chain (steps that follow). Use after context compression to reconstruct the complete workflow timeline.",
|
|
189
236
|
{
|
|
@@ -200,7 +247,7 @@ function buildMcpServer() {
|
|
|
200
247
|
}
|
|
201
248
|
}
|
|
202
249
|
);
|
|
203
|
-
|
|
250
|
+
registerTool(
|
|
204
251
|
"pot_stats",
|
|
205
252
|
"Get PoT statistics: total swaps, turbo/full counts, and turbo ratio for a given period.",
|
|
206
253
|
{ period: import_zod.z.enum(["day", "week", "month"]).describe("Time period for statistics") },
|
|
@@ -214,7 +261,7 @@ function buildMcpServer() {
|
|
|
214
261
|
}
|
|
215
262
|
}
|
|
216
263
|
);
|
|
217
|
-
|
|
264
|
+
registerTool(
|
|
218
265
|
"pot_health",
|
|
219
266
|
"Check PoT system health: time source status, subgraph sync, server uptime, and current mode.",
|
|
220
267
|
{},
|
|
@@ -228,7 +275,7 @@ function buildMcpServer() {
|
|
|
228
275
|
}
|
|
229
276
|
}
|
|
230
277
|
);
|
|
231
|
-
|
|
278
|
+
registerTool(
|
|
232
279
|
"pot_checkpoint",
|
|
233
280
|
"Create a compressed rollup checkpoint of workflow history. Call this periodically to prevent token explosion when recovering from context compression. Returns checkpointId, compressed event history, chainIntact status, and nextCheckpointHint (recommended events before next checkpoint).",
|
|
234
281
|
{
|
|
@@ -254,7 +301,7 @@ async function main() {
|
|
|
254
301
|
await restoreDAGFromRedis();
|
|
255
302
|
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : null;
|
|
256
303
|
if (port) {
|
|
257
|
-
const
|
|
304
|
+
const requestHandler = async (req, res) => {
|
|
258
305
|
if (req.method === "GET" && (req.url === "/health" || req.url === "/ping")) {
|
|
259
306
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
260
307
|
res.end(JSON.stringify({ status: "ok", server: "ttt-mcp", version: "0.3.2" }));
|
|
@@ -309,9 +356,18 @@ async function main() {
|
|
|
309
356
|
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
310
357
|
}
|
|
311
358
|
}
|
|
312
|
-
}
|
|
359
|
+
};
|
|
360
|
+
const certFile = process.env.MCP_TLS_CERT_FILE?.trim();
|
|
361
|
+
const keyFile = process.env.MCP_TLS_KEY_FILE?.trim();
|
|
362
|
+
if (certFile && !keyFile || !certFile && keyFile) {
|
|
363
|
+
throw new Error("MCP_TLS_CERT_FILE and MCP_TLS_KEY_FILE must be configured together");
|
|
364
|
+
}
|
|
365
|
+
const httpServer = certFile && keyFile ? (0, import_https.createServer)({ minVersion: "TLSv1.3", cert: (0, import_fs.readFileSync)(certFile), key: (0, import_fs.readFileSync)(keyFile) }, (req, res) => {
|
|
366
|
+
const exporter = (0, import_tls_exporter.extractTls13Exporter)(req);
|
|
367
|
+
return (0, import_transport_context.withTransportBinding)({ exporter, clientId: String(req.headers["x-ttt-client-id"] ?? ""), sessionId: String(req.headers["x-ttt-session-id"] ?? "") }, () => requestHandler(req, res));
|
|
368
|
+
}) : (0, import_http.createServer)((req, res) => (0, import_transport_context.withTransportBinding)({ clientId: String(req.headers["x-ttt-client-id"] ?? ""), sessionId: String(req.headers["x-ttt-session-id"] ?? "") }, () => requestHandler(req, res)));
|
|
313
369
|
httpServer.listen(port, () => {
|
|
314
|
-
console.error(`[ttt-mcp] OpenTTT MCP Server (HTTP) on port ${port}`);
|
|
370
|
+
console.error(`[ttt-mcp] OpenTTT MCP Server (${certFile ? "HTTPS/TLS1.3" : "HTTP"}) on port ${port}`);
|
|
315
371
|
});
|
|
316
372
|
} else {
|
|
317
373
|
const stdioServer = buildMcpServer();
|
package/dist/pot_record_v08.js
CHANGED
|
@@ -161,7 +161,7 @@ function decodePotRecordV08(record) {
|
|
|
161
161
|
p
|
|
162
162
|
};
|
|
163
163
|
}
|
|
164
|
-
function verifyPotRecordV08(record, ctxId, issuerPublicKeyRaw, content) {
|
|
164
|
+
function verifyPotRecordV08(record, ctxId, issuerPublicKeyRaw, content, freshness) {
|
|
165
165
|
let decoded;
|
|
166
166
|
try {
|
|
167
167
|
decoded = decodePotRecordV08(record);
|
|
@@ -177,6 +177,15 @@ function verifyPotRecordV08(record, ctxId, issuerPublicKeyRaw, content) {
|
|
|
177
177
|
if (decoded.errorBoundUs === RESERVED_ERROR_BOUND) {
|
|
178
178
|
return { verdict: "rejected", reason: "Error Bound carries the reserved value 0xFFFFFF" };
|
|
179
179
|
}
|
|
180
|
+
if (freshness !== void 0) {
|
|
181
|
+
if (freshness.maxSkewNs < 0n) {
|
|
182
|
+
return { verdict: "rejected", reason: "invalid freshness policy" };
|
|
183
|
+
}
|
|
184
|
+
const delta = decoded.timestampNs >= freshness.nowNs ? decoded.timestampNs - freshness.nowNs : freshness.nowNs - decoded.timestampNs;
|
|
185
|
+
if (delta > freshness.maxSkewNs + BigInt(decoded.errorBoundUs) * 1000n) {
|
|
186
|
+
return { verdict: "rejected", reason: "freshness window exceeded" };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
180
189
|
const expectedCommitment = computeCommitmentSha256(decoded.fieldsPre, ctxId);
|
|
181
190
|
if (!expectedCommitment.equals(decoded.commitment)) {
|
|
182
191
|
return { verdict: "rejected", reason: "commitment mismatch" };
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var pot_record_v2_exports = {};
|
|
20
|
+
__export(pot_record_v2_exports, {
|
|
21
|
+
HOLDER_AUTH_ED25519: () => HOLDER_AUTH_ED25519,
|
|
22
|
+
HOLDER_AUTH_SHARED_SECRET: () => HOLDER_AUTH_SHARED_SECRET,
|
|
23
|
+
POT_V2_INTEGRITY_SHA256: () => POT_V2_INTEGRITY_SHA256,
|
|
24
|
+
POT_V2_SIZE: () => POT_V2_SIZE,
|
|
25
|
+
POT_V2_VERSION: () => POT_V2_VERSION,
|
|
26
|
+
computeV2BindingInput: () => computeV2BindingInput,
|
|
27
|
+
computeV2ExporterContext: () => computeV2ExporterContext,
|
|
28
|
+
decodePotRecordV2: () => decodePotRecordV2,
|
|
29
|
+
encodePotRecordV2: () => encodePotRecordV2,
|
|
30
|
+
verifyPotRecordV2: () => verifyPotRecordV2,
|
|
31
|
+
verifyV2BindingProof: () => verifyV2BindingProof
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(pot_record_v2_exports);
|
|
34
|
+
var import_crypto = require("crypto");
|
|
35
|
+
const POT_V2_SIZE = 180;
|
|
36
|
+
const POT_V2_VERSION = 2;
|
|
37
|
+
const POT_V2_INTEGRITY_SHA256 = 1;
|
|
38
|
+
const HOLDER_AUTH_ED25519 = 1;
|
|
39
|
+
const HOLDER_AUTH_SHARED_SECRET = 2;
|
|
40
|
+
const OFF = {
|
|
41
|
+
VERSION: 0,
|
|
42
|
+
HOLDER_AUTH_TYPE: 1,
|
|
43
|
+
ALG_ID: 2,
|
|
44
|
+
TS: 4,
|
|
45
|
+
DISPERSION: 12,
|
|
46
|
+
CTX_ID: 16,
|
|
47
|
+
NONCE: 32,
|
|
48
|
+
HOLDER_AUTH_DATA: 48,
|
|
49
|
+
INTEGRITY_TAG: 80,
|
|
50
|
+
ISSUER_KEY_ID: 112,
|
|
51
|
+
ISSUER_SIG: 116
|
|
52
|
+
};
|
|
53
|
+
const ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
|
|
54
|
+
function requireLength(name, value, length) {
|
|
55
|
+
if (value.length !== length) throw new Error(`${name} must be ${length} bytes`);
|
|
56
|
+
}
|
|
57
|
+
function issuerPublicKeyFromRaw(raw) {
|
|
58
|
+
requireLength("issuer public key", raw, 32);
|
|
59
|
+
return (0, import_crypto.createPublicKey)({ key: Buffer.concat([ED25519_SPKI_PREFIX, raw]), format: "der", type: "spki" });
|
|
60
|
+
}
|
|
61
|
+
function integrityTag(signedPrefix) {
|
|
62
|
+
return (0, import_crypto.createHash)("sha256").update(signedPrefix).digest();
|
|
63
|
+
}
|
|
64
|
+
function encodePotRecordV2(fields, issuerPrivateKey) {
|
|
65
|
+
if (fields.holderAuthType !== HOLDER_AUTH_ED25519 && fields.holderAuthType !== HOLDER_AUTH_SHARED_SECRET) {
|
|
66
|
+
throw new Error("unsupported holder_auth_type");
|
|
67
|
+
}
|
|
68
|
+
if (fields.algId !== POT_V2_INTEGRITY_SHA256) throw new Error("unsupported alg_id");
|
|
69
|
+
if (fields.tsTaiUs < 0n || fields.tsTaiUs > 0xffffffffffffffffn) throw new Error("ts out of range");
|
|
70
|
+
if (fields.dispersionUs < 0 || fields.dispersionUs > 4294967295) throw new Error("dispersion out of range");
|
|
71
|
+
if (fields.issuerKeyId < 0 || fields.issuerKeyId > 4294967295) throw new Error("issuer_key_id out of range");
|
|
72
|
+
requireLength("ctx_id", fields.ctxId, 16);
|
|
73
|
+
requireLength("nonce", fields.nonce, 16);
|
|
74
|
+
requireLength("holder_auth_data", fields.holderAuthData, 32);
|
|
75
|
+
const prefix = Buffer.alloc(116);
|
|
76
|
+
prefix.writeUInt8(POT_V2_VERSION, OFF.VERSION);
|
|
77
|
+
prefix.writeUInt8(fields.holderAuthType, OFF.HOLDER_AUTH_TYPE);
|
|
78
|
+
prefix.writeUInt16BE(fields.algId, OFF.ALG_ID);
|
|
79
|
+
prefix.writeBigUInt64BE(fields.tsTaiUs, OFF.TS);
|
|
80
|
+
prefix.writeUInt32BE(fields.dispersionUs, OFF.DISPERSION);
|
|
81
|
+
fields.ctxId.copy(prefix, OFF.CTX_ID);
|
|
82
|
+
fields.nonce.copy(prefix, OFF.NONCE);
|
|
83
|
+
fields.holderAuthData.copy(prefix, OFF.HOLDER_AUTH_DATA);
|
|
84
|
+
integrityTag(prefix.subarray(0, 80)).copy(prefix, OFF.INTEGRITY_TAG);
|
|
85
|
+
prefix.writeUInt32BE(fields.issuerKeyId, OFF.ISSUER_KEY_ID);
|
|
86
|
+
const signature = (0, import_crypto.sign)(null, prefix, issuerPrivateKey);
|
|
87
|
+
return Buffer.concat([prefix, signature]);
|
|
88
|
+
}
|
|
89
|
+
function decodePotRecordV2(record) {
|
|
90
|
+
if (record.length !== POT_V2_SIZE) throw new Error(`expected 180 octets, got ${record.length}`);
|
|
91
|
+
const signedPrefix = record.subarray(0, 116);
|
|
92
|
+
return {
|
|
93
|
+
holderAuthType: record.readUInt8(OFF.HOLDER_AUTH_TYPE),
|
|
94
|
+
algId: record.readUInt16BE(OFF.ALG_ID),
|
|
95
|
+
tsTaiUs: record.readBigUInt64BE(OFF.TS),
|
|
96
|
+
dispersionUs: record.readUInt32BE(OFF.DISPERSION),
|
|
97
|
+
ctxId: Buffer.from(record.subarray(OFF.CTX_ID, OFF.CTX_ID + 16)),
|
|
98
|
+
nonce: Buffer.from(record.subarray(OFF.NONCE, OFF.NONCE + 16)),
|
|
99
|
+
holderAuthData: Buffer.from(record.subarray(OFF.HOLDER_AUTH_DATA, OFF.HOLDER_AUTH_DATA + 32)),
|
|
100
|
+
issuerKeyId: record.readUInt32BE(OFF.ISSUER_KEY_ID),
|
|
101
|
+
integrityTag: Buffer.from(record.subarray(OFF.INTEGRITY_TAG, OFF.INTEGRITY_TAG + 32)),
|
|
102
|
+
issuerSig: Buffer.from(record.subarray(OFF.ISSUER_SIG, POT_V2_SIZE)),
|
|
103
|
+
signedPrefix: Buffer.from(signedPrefix)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function computeV2ExporterContext(record) {
|
|
107
|
+
const decoded = decodePotRecordV2(record);
|
|
108
|
+
const bindingPrefix = Buffer.from("tttps-binding-v2\0", "ascii");
|
|
109
|
+
const recordHash = (0, import_crypto.createHash)("sha256").update(record).digest();
|
|
110
|
+
return (0, import_crypto.createHash)("sha256").update(Buffer.concat([bindingPrefix, recordHash, decoded.ctxId, decoded.nonce])).digest();
|
|
111
|
+
}
|
|
112
|
+
function computeV2BindingInput(record, exporterOutput) {
|
|
113
|
+
decodePotRecordV2(record);
|
|
114
|
+
if (exporterOutput.length !== 32) throw new Error("TLS exporter output must be 32 octets");
|
|
115
|
+
return Buffer.concat([Buffer.from("tttps-binding-v2\0", "ascii"), exporterOutput, record.subarray(16, 32), record.subarray(32, 48)]);
|
|
116
|
+
}
|
|
117
|
+
function verifyV2BindingProof(record, bindingProof, exporterOutput, sharedSecret) {
|
|
118
|
+
const decoded = decodePotRecordV2(record);
|
|
119
|
+
const bindingInput = computeV2BindingInput(record, exporterOutput);
|
|
120
|
+
if (decoded.holderAuthType === HOLDER_AUTH_ED25519) {
|
|
121
|
+
if (bindingProof.length !== 64) return { verdict: "rejected", reason: "Ed25519 binding proof must be 64 octets" };
|
|
122
|
+
const holderPublicKey = issuerPublicKeyFromRaw(decoded.holderAuthData);
|
|
123
|
+
return (0, import_crypto.verify)(null, bindingInput, holderPublicKey, bindingProof) ? { verdict: "intact" } : { verdict: "rejected", reason: "TLS exporter holder proof mismatch" };
|
|
124
|
+
}
|
|
125
|
+
if (decoded.holderAuthType === HOLDER_AUTH_SHARED_SECRET) {
|
|
126
|
+
if (!sharedSecret) return { verdict: "rejected", reason: "shared-secret holder binding is not configured" };
|
|
127
|
+
if (bindingProof.length !== 32) return { verdict: "rejected", reason: "HMAC binding proof must be 32 octets" };
|
|
128
|
+
const expectedDigest = (0, import_crypto.createHash)("sha256").update(sharedSecret).digest();
|
|
129
|
+
if (!(0, import_crypto.timingSafeEqual)(expectedDigest, decoded.holderAuthData)) return { verdict: "rejected", reason: "shared-secret digest mismatch" };
|
|
130
|
+
const expected = (0, import_crypto.createHmac)("sha256", sharedSecret).update(bindingInput).digest();
|
|
131
|
+
return (0, import_crypto.timingSafeEqual)(expected, bindingProof) ? { verdict: "intact" } : { verdict: "rejected", reason: "TLS exporter HMAC proof mismatch" };
|
|
132
|
+
}
|
|
133
|
+
return { verdict: "rejected", reason: "unsupported holder authentication type" };
|
|
134
|
+
}
|
|
135
|
+
function verifyPotRecordV2(record, issuerPublicKeyRaw, freshness) {
|
|
136
|
+
let decoded;
|
|
137
|
+
try {
|
|
138
|
+
decoded = decodePotRecordV2(record);
|
|
139
|
+
} catch (e) {
|
|
140
|
+
return { verdict: "rejected", reason: e instanceof Error ? e.message : String(e) };
|
|
141
|
+
}
|
|
142
|
+
if (record[OFF.VERSION] !== POT_V2_VERSION) return { verdict: "rejected", reason: "unknown version" };
|
|
143
|
+
if (decoded.holderAuthType !== HOLDER_AUTH_ED25519 && decoded.holderAuthType !== HOLDER_AUTH_SHARED_SECRET) {
|
|
144
|
+
return { verdict: "rejected", reason: "unsupported holder_auth_type" };
|
|
145
|
+
}
|
|
146
|
+
if (decoded.algId !== POT_V2_INTEGRITY_SHA256) return { verdict: "rejected", reason: "unsupported alg_id" };
|
|
147
|
+
if (!integrityTag(decoded.signedPrefix.subarray(0, 80)).equals(decoded.integrityTag)) return { verdict: "rejected", reason: "integrity mismatch" };
|
|
148
|
+
if (freshness) {
|
|
149
|
+
const delta = decoded.tsTaiUs >= freshness.nowTaiUs ? decoded.tsTaiUs - freshness.nowTaiUs : freshness.nowTaiUs - decoded.tsTaiUs;
|
|
150
|
+
if (delta > freshness.maxSkewUs + BigInt(decoded.dispersionUs)) return { verdict: "rejected", reason: "freshness window exceeded" };
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
if (!(0, import_crypto.verify)(null, decoded.signedPrefix, issuerPublicKeyFromRaw(issuerPublicKeyRaw), decoded.issuerSig)) {
|
|
154
|
+
return { verdict: "rejected", reason: "issuer signature invalid" };
|
|
155
|
+
}
|
|
156
|
+
} catch (e) {
|
|
157
|
+
return { verdict: "rejected", reason: e instanceof Error ? e.message : String(e) };
|
|
158
|
+
}
|
|
159
|
+
return { verdict: "intact", nonce: decoded.nonce };
|
|
160
|
+
}
|
|
161
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
162
|
+
0 && (module.exports = {
|
|
163
|
+
HOLDER_AUTH_ED25519,
|
|
164
|
+
HOLDER_AUTH_SHARED_SECRET,
|
|
165
|
+
POT_V2_INTEGRITY_SHA256,
|
|
166
|
+
POT_V2_SIZE,
|
|
167
|
+
POT_V2_VERSION,
|
|
168
|
+
computeV2BindingInput,
|
|
169
|
+
computeV2ExporterContext,
|
|
170
|
+
decodePotRecordV2,
|
|
171
|
+
encodePotRecordV2,
|
|
172
|
+
verifyPotRecordV2,
|
|
173
|
+
verifyV2BindingProof
|
|
174
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var tls_exporter_exports = {};
|
|
20
|
+
__export(tls_exporter_exports, {
|
|
21
|
+
TTTPS_V2_EXPORTER_LABEL: () => TTTPS_V2_EXPORTER_LABEL,
|
|
22
|
+
TTTPS_V2_EXPORTER_LENGTH: () => TTTPS_V2_EXPORTER_LENGTH,
|
|
23
|
+
exporterForTlsSocket: () => exporterForTlsSocket,
|
|
24
|
+
extractTls13Exporter: () => extractTls13Exporter
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(tls_exporter_exports);
|
|
27
|
+
const TTTPS_V2_EXPORTER_LABEL = "EXPORTER-TTTPS-v2-Binding";
|
|
28
|
+
const TTTPS_V2_EXPORTER_LENGTH = 32;
|
|
29
|
+
function exporterForTlsSocket(socket) {
|
|
30
|
+
if (!socket.encrypted || typeof socket.exportKeyingMaterial !== "function") return void 0;
|
|
31
|
+
if (typeof socket.getProtocol === "function" && socket.getProtocol() !== "TLSv1.3") return void 0;
|
|
32
|
+
return (contextValue) => socket.exportKeyingMaterial(TTTPS_V2_EXPORTER_LENGTH, TTTPS_V2_EXPORTER_LABEL, contextValue);
|
|
33
|
+
}
|
|
34
|
+
function extractTls13Exporter(req) {
|
|
35
|
+
return exporterForTlsSocket(req.socket);
|
|
36
|
+
}
|
|
37
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
38
|
+
0 && (module.exports = {
|
|
39
|
+
TTTPS_V2_EXPORTER_LABEL,
|
|
40
|
+
TTTPS_V2_EXPORTER_LENGTH,
|
|
41
|
+
exporterForTlsSocket,
|
|
42
|
+
extractTls13Exporter
|
|
43
|
+
});
|
package/dist/tools.js
CHANGED
|
@@ -30,12 +30,14 @@ var tools_exports = {};
|
|
|
30
30
|
__export(tools_exports, {
|
|
31
31
|
potCheckpoint: () => potCheckpoint,
|
|
32
32
|
potGenerate: () => potGenerate,
|
|
33
|
+
potGenerateV2: () => potGenerateV2,
|
|
33
34
|
potGraph: () => potGraph,
|
|
34
35
|
potHealth: () => potHealth,
|
|
35
36
|
potQuery: () => potQuery,
|
|
36
37
|
potStats: () => potStats,
|
|
37
38
|
potVerify: () => potVerify,
|
|
38
39
|
potVerifyV08: () => potVerifyV08,
|
|
40
|
+
potVerifyV2: () => potVerifyV2,
|
|
39
41
|
redis: () => redis,
|
|
40
42
|
restoreDagEntry: () => restoreDagEntry,
|
|
41
43
|
tttsFreshnessSeal: () => tttsFreshnessSeal,
|
|
@@ -46,6 +48,8 @@ var import_openttt = require("openttt");
|
|
|
46
48
|
var import_telemetry = require("./telemetry");
|
|
47
49
|
var import_server = require("./server");
|
|
48
50
|
var import_crypto = require("crypto");
|
|
51
|
+
var import_pot_record_v2 = require("./pot_record_v2");
|
|
52
|
+
var import_transport_context = require("./transport_context");
|
|
49
53
|
var import_pot_record_v08 = require("./pot_record_v08");
|
|
50
54
|
var import_ioredis = __toESM(require("ioredis"));
|
|
51
55
|
const GrgPipeline = (
|
|
@@ -71,6 +75,41 @@ const redis = new import_ioredis.default(process.env.REDIS_URL ?? "redis://127.0
|
|
|
71
75
|
});
|
|
72
76
|
redis.on("error", () => {
|
|
73
77
|
});
|
|
78
|
+
const REPLAY_TTL_SECONDS = Number.parseInt(process.env.TTTPS_REPLAY_TTL_SECONDS ?? "86400", 10);
|
|
79
|
+
function requiredFlag(name) {
|
|
80
|
+
return process.env[name]?.trim() === "1";
|
|
81
|
+
}
|
|
82
|
+
function configuredFreshnessPolicy() {
|
|
83
|
+
const raw = process.env.TTTPS_V08_MAX_SKEW_NS?.trim();
|
|
84
|
+
if (!raw) return void 0;
|
|
85
|
+
try {
|
|
86
|
+
const maxSkewNs = BigInt(raw);
|
|
87
|
+
if (maxSkewNs < 0n) return void 0;
|
|
88
|
+
return { nowNs: BigInt(Date.now()) * 1000000n, maxSkewNs };
|
|
89
|
+
} catch {
|
|
90
|
+
return void 0;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function claimV2Replay(ctxIdHex, nonceHex) {
|
|
94
|
+
if (!ctxIdHex || !nonceHex) return "unavailable";
|
|
95
|
+
const key = `tttps:v2:replay:${ctxIdHex}:${nonceHex}`;
|
|
96
|
+
try {
|
|
97
|
+
const result = await redis.set(key, "1", "EX", REPLAY_TTL_SECONDS, "NX");
|
|
98
|
+
return result === "OK" ? "claimed" : "replay";
|
|
99
|
+
} catch {
|
|
100
|
+
return "unavailable";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async function claimV08Replay(clientId, sessionId, nonceHex) {
|
|
104
|
+
if (!clientId || !sessionId || !nonceHex) return "unavailable";
|
|
105
|
+
const key = `tttps:v08:replay:${clientId}:${sessionId}:${nonceHex}`;
|
|
106
|
+
try {
|
|
107
|
+
const result = await redis.set(key, "1", "EX", REPLAY_TTL_SECONDS, "NX");
|
|
108
|
+
return result === "OK" ? "claimed" : "replay";
|
|
109
|
+
} catch {
|
|
110
|
+
return "unavailable";
|
|
111
|
+
}
|
|
112
|
+
}
|
|
74
113
|
const timeSynth = new import_openttt.TimeSynthesis();
|
|
75
114
|
const adaptiveSwitch = new import_openttt.AdaptiveSwitch();
|
|
76
115
|
const potSigner = new import_openttt.PotSigner();
|
|
@@ -374,17 +413,104 @@ async function potVerify(args) {
|
|
|
374
413
|
verifiedAt: Date.now()
|
|
375
414
|
});
|
|
376
415
|
}
|
|
416
|
+
async function potGenerateV2(args) {
|
|
417
|
+
(0, import_telemetry.telemetryIncrement)("pot_generate_v2");
|
|
418
|
+
const ctxId = Buffer.from(args.ctxId, "hex");
|
|
419
|
+
const holderAuthData = Buffer.from(args.holderAuthData, "hex");
|
|
420
|
+
if (ctxId.length !== 16) throw new Error("ctxId must be exactly 16 bytes of hex");
|
|
421
|
+
if (holderAuthData.length !== 32) throw new Error("holderAuthData must be exactly 32 bytes of hex");
|
|
422
|
+
const tsTaiUs = BigInt(args.tsTaiUs);
|
|
423
|
+
const nonce = (0, import_crypto.randomBytes)(16);
|
|
424
|
+
const issuerKeyId = (0, import_crypto.createHash)("sha256").update(potSignerPublicKeyRawV08).digest().readUInt32BE(0);
|
|
425
|
+
const record = (0, import_pot_record_v2.encodePotRecordV2)({
|
|
426
|
+
holderAuthType: args.holderAuthType ?? 1,
|
|
427
|
+
algId: 1,
|
|
428
|
+
tsTaiUs,
|
|
429
|
+
dispersionUs: args.dispersionUs,
|
|
430
|
+
ctxId,
|
|
431
|
+
nonce,
|
|
432
|
+
holderAuthData,
|
|
433
|
+
issuerKeyId
|
|
434
|
+
}, potSignerPrivateKeyV08);
|
|
435
|
+
return serialize({
|
|
436
|
+
wireProfile: "draft-helmprotocol-tttps-11-v2",
|
|
437
|
+
potRecordV2: record.toString("hex"),
|
|
438
|
+
issuerPubKey: potSignerPublicKeyRawV08.toString("hex"),
|
|
439
|
+
issuerKeyId,
|
|
440
|
+
nonce: nonce.toString("hex"),
|
|
441
|
+
bindingProofRequired: process.env.TTTPS_V2_REQUIRE_BINDING === "1"
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
async function potVerifyV2(args) {
|
|
445
|
+
(0, import_telemetry.telemetryIncrement)("pot_verify_v2");
|
|
446
|
+
const bindingRequired = process.env.TTTPS_V2_REQUIRE_BINDING === "1";
|
|
447
|
+
if (bindingRequired && !args.bindingProof) {
|
|
448
|
+
return serialize({ verdict: "rejected", reason: "TLS exporter binding proof is required" });
|
|
449
|
+
}
|
|
450
|
+
const record = Buffer.from(args.potRecordV2, "hex");
|
|
451
|
+
const issuerPubKey = args.issuerPubKey ? Buffer.from(args.issuerPubKey, "hex") : potSignerPublicKeyRawV08;
|
|
452
|
+
const freshness = args.nowTaiUs !== void 0 && args.maxSkewUs !== void 0 ? { nowTaiUs: BigInt(args.nowTaiUs), maxSkewUs: BigInt(args.maxSkewUs) } : void 0;
|
|
453
|
+
const result = (0, import_pot_record_v2.verifyPotRecordV2)(record, issuerPubKey, freshness);
|
|
454
|
+
if (result.verdict === "rejected") return serialize(result);
|
|
455
|
+
if (args.bindingProof) {
|
|
456
|
+
const exporter = (0, import_transport_context.currentTransportBinding)()?.exporter;
|
|
457
|
+
if (!exporter) return serialize({ verdict: "rejected", reason: "live TLS exporter unavailable" });
|
|
458
|
+
let exporterOutput;
|
|
459
|
+
try {
|
|
460
|
+
exporterOutput = exporter((0, import_pot_record_v2.computeV2ExporterContext)(record));
|
|
461
|
+
} catch {
|
|
462
|
+
return serialize({ verdict: "rejected", reason: "TLS exporter derivation failed" });
|
|
463
|
+
}
|
|
464
|
+
const binding = (0, import_pot_record_v2.verifyV2BindingProof)(record, Buffer.from(args.bindingProof, "hex"), exporterOutput);
|
|
465
|
+
if (binding.verdict === "rejected") return serialize(binding);
|
|
466
|
+
}
|
|
467
|
+
if (process.env.TTTPS_REQUIRE_REPLAY_LEDGER === "1") {
|
|
468
|
+
const ctxId = record.subarray(16, 32).toString("hex");
|
|
469
|
+
const replay = await claimV2Replay(ctxId, result.nonce.toString("hex"));
|
|
470
|
+
if (replay !== "claimed") return serialize({ verdict: "rejected", reason: replay === "replay" ? "replay detected" : "replay ledger unavailable" });
|
|
471
|
+
}
|
|
472
|
+
return serialize({ verdict: "intact", wireProfile: "draft-helmprotocol-tttps-11-v2", nonce: result.nonce.toString("hex") });
|
|
473
|
+
}
|
|
377
474
|
async function potVerifyV08(args) {
|
|
378
475
|
(0, import_telemetry.telemetryIncrement)("pot_verify_v08");
|
|
379
476
|
const record = Buffer.from(args.potRecordV08, "hex");
|
|
380
477
|
const ctxId = args.ctxId ?? DEFAULT_CTX_ID_V08;
|
|
381
478
|
const issuerPubKey = args.issuerPubKey !== void 0 ? Buffer.from(args.issuerPubKey, "hex") : potSignerPublicKeyRawV08;
|
|
382
479
|
const content = args.content !== void 0 ? Buffer.from(args.content, "utf8") : void 0;
|
|
383
|
-
const
|
|
480
|
+
const requireFreshness = requiredFlag("TTTPS_REQUIRE_V08_FRESHNESS");
|
|
481
|
+
const freshness = configuredFreshnessPolicy();
|
|
482
|
+
if (requireFreshness && freshness === void 0) {
|
|
483
|
+
return serialize({ verdict: "rejected", reason: "freshness policy is not configured", ctxId, verifiedAt: Date.now() });
|
|
484
|
+
}
|
|
485
|
+
const result = (0, import_pot_record_v08.verifyPotRecordV08)(record, ctxId, issuerPubKey, content, freshness);
|
|
486
|
+
if (result.verdict === "rejected") {
|
|
487
|
+
return serialize({
|
|
488
|
+
verdict: result.verdict,
|
|
489
|
+
reason: result.reason ?? null,
|
|
490
|
+
payloadDigestMatchesContent: result.payloadDigestMatchesContent ?? null,
|
|
491
|
+
ctxId,
|
|
492
|
+
verifiedAt: Date.now()
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
let replayClaim = "not_required";
|
|
496
|
+
if (requiredFlag("TTTPS_REQUIRE_REPLAY_LEDGER")) {
|
|
497
|
+
const decoded = (0, import_pot_record_v08.decodePotRecordV08)(record);
|
|
498
|
+
replayClaim = await claimV08Replay(args.clientId ?? "", args.sessionId ?? "", decoded.nonce.toString("hex"));
|
|
499
|
+
if (replayClaim !== "claimed") {
|
|
500
|
+
return serialize({
|
|
501
|
+
verdict: "rejected",
|
|
502
|
+
reason: replayClaim === "replay" ? "replay detected" : "replay ledger unavailable",
|
|
503
|
+
replayClaim,
|
|
504
|
+
ctxId,
|
|
505
|
+
verifiedAt: Date.now()
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
}
|
|
384
509
|
return serialize({
|
|
385
510
|
verdict: result.verdict,
|
|
386
511
|
reason: result.reason ?? null,
|
|
387
512
|
payloadDigestMatchesContent: result.payloadDigestMatchesContent ?? null,
|
|
513
|
+
replayClaim,
|
|
388
514
|
ctxId,
|
|
389
515
|
verifiedAt: Date.now()
|
|
390
516
|
});
|
|
@@ -645,12 +771,14 @@ async function potCheckpoint(args) {
|
|
|
645
771
|
0 && (module.exports = {
|
|
646
772
|
potCheckpoint,
|
|
647
773
|
potGenerate,
|
|
774
|
+
potGenerateV2,
|
|
648
775
|
potGraph,
|
|
649
776
|
potHealth,
|
|
650
777
|
potQuery,
|
|
651
778
|
potStats,
|
|
652
779
|
potVerify,
|
|
653
780
|
potVerifyV08,
|
|
781
|
+
potVerifyV2,
|
|
654
782
|
redis,
|
|
655
783
|
restoreDagEntry,
|
|
656
784
|
tttsFreshnessSeal,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var transport_context_exports = {};
|
|
20
|
+
__export(transport_context_exports, {
|
|
21
|
+
currentTransportBinding: () => currentTransportBinding,
|
|
22
|
+
withTransportBinding: () => withTransportBinding
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(transport_context_exports);
|
|
25
|
+
var import_node_async_hooks = require("node:async_hooks");
|
|
26
|
+
const storage = new import_node_async_hooks.AsyncLocalStorage();
|
|
27
|
+
function withTransportBinding(context, fn) {
|
|
28
|
+
return storage.run(context, fn);
|
|
29
|
+
}
|
|
30
|
+
function currentTransportBinding() {
|
|
31
|
+
return storage.getStore();
|
|
32
|
+
}
|
|
33
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
34
|
+
0 && (module.exports = {
|
|
35
|
+
currentTransportBinding,
|
|
36
|
+
withTransportBinding
|
|
37
|
+
});
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helm-protocol/ttt-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Proof-of-Time attestation
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Proof-of-Time attestation — Ed25519-signed timestamps with multi-source corroboration and explicit error bounds. IETF draft-helmprotocol-tttps",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"ttt-mcp": "
|
|
7
|
+
"ttt-mcp": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
11
|
"README.md"
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
|
-
"build": "esbuild index.ts tools.ts telemetry.ts auth.ts server.ts pot_record_v08.ts --platform=node --target=node18 --format=cjs --outdir=dist",
|
|
14
|
+
"build": "esbuild index.ts tools.ts telemetry.ts auth.ts server.ts pot_record_v08.ts pot_record_v2.ts tls_exporter.ts transport_context.ts --platform=node --target=node18 --format=cjs --outdir=dist",
|
|
15
15
|
"start": "node dist/index.js",
|
|
16
16
|
"dev": "npx ts-node index.ts",
|
|
17
17
|
"test": "jest --forceExit",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"license": "BSL-1.1",
|
|
51
51
|
"repository": {
|
|
52
52
|
"type": "git",
|
|
53
|
-
"url": "https://github.com/Helm-Protocol/openttt-mcp"
|
|
53
|
+
"url": "git+https://github.com/Helm-Protocol/openttt-mcp.git"
|
|
54
54
|
},
|
|
55
55
|
"homepage": "https://github.com/Helm-Protocol/openttt-mcp#readme",
|
|
56
56
|
"engines": {
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
61
61
|
"ioredis": "^5.11.0",
|
|
62
|
-
"openttt": "^0.
|
|
62
|
+
"openttt": "^0.3.1",
|
|
63
63
|
"zod": "^3.25.0"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
@@ -70,5 +70,8 @@
|
|
|
70
70
|
"ts-jest": "^29.4.11",
|
|
71
71
|
"typescript": "^5.3.3"
|
|
72
72
|
},
|
|
73
|
-
"mcpName": "io.github.Helm-Protocol/openttt-pot"
|
|
73
|
+
"mcpName": "io.github.Helm-Protocol/openttt-pot",
|
|
74
|
+
"publishConfig": {
|
|
75
|
+
"access": "public"
|
|
76
|
+
}
|
|
74
77
|
}
|