@helm-protocol/ttt-mcp 0.3.2 → 0.3.4

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 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.0
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.0
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.0"],
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
- [![Add to Cursor](https://img.shields.io/badge/Add%20to%20Cursor-1a1a1a?style=flat&logo=cursor&logoColor=white)](https://cursor.com/install-mcp?name=ttt&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBoZWxtLXByb3RvY29sL3R0dC1tY3BAMC4zLjAiXX0=)
84
+ [![Add to Cursor](https://img.shields.io/badge/Add%20to%20Cursor-1a1a1a?style=flat&logo=cursor&logoColor=white)](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
 
@@ -0,0 +1,212 @@
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_v08_exports = {};
20
+ __export(pot_record_v08_exports, {
21
+ MTI_INTEGRITY_ALG_SHA256: () => MTI_INTEGRITY_ALG_SHA256,
22
+ RECORD_SIZE_HOLDER: () => RECORD_SIZE_HOLDER,
23
+ RECORD_SIZE_NO_HOLDER: () => RECORD_SIZE_NO_HOLDER,
24
+ RESERVED_ERROR_BOUND: () => RESERVED_ERROR_BOUND,
25
+ assemblePotRecordV08: () => assemblePotRecordV08,
26
+ computeCommitmentSha256: () => computeCommitmentSha256,
27
+ decodePotRecordV08: () => decodePotRecordV08,
28
+ ed25519KeyFromSeed: () => ed25519KeyFromSeed,
29
+ ed25519PublicKeyFromRaw: () => ed25519PublicKeyFromRaw,
30
+ generatePotRecordV08: () => generatePotRecordV08,
31
+ rawPublicKeyOf: () => rawPublicKeyOf,
32
+ verifyPotRecordV08: () => verifyPotRecordV08
33
+ });
34
+ module.exports = __toCommonJS(pot_record_v08_exports);
35
+ var import_crypto = require("crypto");
36
+ const ED25519_PKCS8_PREFIX = Buffer.from("302e020100300506032b657004220420", "hex");
37
+ const ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
38
+ function ed25519KeyFromSeed(seed) {
39
+ if (seed.length !== 32) throw new Error("Ed25519 seed must be 32 bytes");
40
+ const der = Buffer.concat([ED25519_PKCS8_PREFIX, seed]);
41
+ return (0, import_crypto.createPrivateKey)({ key: der, format: "der", type: "pkcs8" });
42
+ }
43
+ function ed25519PublicKeyFromRaw(pub) {
44
+ if (pub.length !== 32) throw new Error("Ed25519 public key must be 32 bytes");
45
+ const der = Buffer.concat([ED25519_SPKI_PREFIX, pub]);
46
+ return (0, import_crypto.createPublicKey)({ key: der, format: "der", type: "spki" });
47
+ }
48
+ function rawPublicKeyOf(key) {
49
+ return key.export({ type: "spki", format: "der" }).subarray(-32);
50
+ }
51
+ const PRE_OFFSET = {
52
+ HEADER: 0,
53
+ // Version(4)|Tier(4), Flags(8), Integrity Alg(16) — 4 bytes
54
+ SRC_ERR: 4,
55
+ // Src Cnt(8), Error Bound(24) — 4 bytes
56
+ TIMESTAMP: 8,
57
+ // 8 bytes
58
+ ISSUER_KEY_ID: 16,
59
+ // 8 bytes
60
+ NONCE: 24,
61
+ // 32 bytes
62
+ PAYLOAD_DIGEST: 56,
63
+ // 32 bytes
64
+ HOLDER_KEY: 88
65
+ // 32 bytes, iff Flags bit 0 = 1
66
+ };
67
+ const PRE_LEN_NO_HOLDER = 88;
68
+ const PRE_LEN_HOLDER = 120;
69
+ const COMMITMENT_LEN = 32;
70
+ const SIGNATURE_LEN = 64;
71
+ const RECORD_SIZE_NO_HOLDER = PRE_LEN_NO_HOLDER + COMMITMENT_LEN + SIGNATURE_LEN;
72
+ const RECORD_SIZE_HOLDER = PRE_LEN_HOLDER + COMMITMENT_LEN + SIGNATURE_LEN;
73
+ const MTI_INTEGRITY_ALG_SHA256 = 1;
74
+ const RESERVED_ERROR_BOUND = 16777215;
75
+ function encodeFieldsPre(f) {
76
+ if (f.version < 0 || f.version > 15) throw new Error("version must fit in 4 bits");
77
+ if (f.tier < 0 || f.tier > 15) throw new Error("tier must fit in 4 bits");
78
+ if (f.integrityAlg < 0 || f.integrityAlg > 65535) throw new Error("integrityAlg must fit in 16 bits");
79
+ if (f.srcCnt < 3) throw new Error("Src Cnt MUST be at least 3 (draft-08 Section 3.2)");
80
+ if (f.srcCnt > 255) throw new Error("srcCnt must fit in 8 bits");
81
+ if (f.errorBoundUs === RESERVED_ERROR_BOUND) {
82
+ throw new Error("Error Bound 0xFFFFFF is reserved and MUST NOT be emitted (draft-08 Section 3.2)");
83
+ }
84
+ if (f.errorBoundUs < 0 || f.errorBoundUs > 16777215) throw new Error("errorBoundUs must fit in 24 bits");
85
+ if (f.issuerKeyId.length !== 8) throw new Error("issuerKeyId must be 8 bytes");
86
+ if (f.nonce.length !== 32) throw new Error("nonce must be 32 bytes");
87
+ if (f.payloadDigest.length !== 32) throw new Error("payloadDigest must be 32 bytes (SHA-256)");
88
+ if (f.holderKey !== void 0 && f.holderKey.length !== 32) {
89
+ throw new Error("holderKey must be 32 bytes");
90
+ }
91
+ const hasHolder = f.holderKey !== void 0;
92
+ const buf = Buffer.alloc(hasHolder ? PRE_LEN_HOLDER : PRE_LEN_NO_HOLDER);
93
+ buf.writeUInt8(f.version << 4 | f.tier, PRE_OFFSET.HEADER);
94
+ buf.writeUInt8(hasHolder ? 128 : 0, PRE_OFFSET.HEADER + 1);
95
+ buf.writeUInt16BE(f.integrityAlg, PRE_OFFSET.HEADER + 2);
96
+ buf.writeUInt8(f.srcCnt, PRE_OFFSET.SRC_ERR);
97
+ buf.writeUIntBE(f.errorBoundUs, PRE_OFFSET.SRC_ERR + 1, 3);
98
+ buf.writeBigUInt64BE(f.timestampNs, PRE_OFFSET.TIMESTAMP);
99
+ f.issuerKeyId.copy(buf, PRE_OFFSET.ISSUER_KEY_ID);
100
+ f.nonce.copy(buf, PRE_OFFSET.NONCE);
101
+ f.payloadDigest.copy(buf, PRE_OFFSET.PAYLOAD_DIGEST);
102
+ if (hasHolder) f.holderKey.copy(buf, PRE_OFFSET.HOLDER_KEY);
103
+ return buf;
104
+ }
105
+ function computeCommitmentSha256(fieldsPre, ctxId) {
106
+ const ctxBytes = Buffer.from(ctxId, "utf8");
107
+ if (ctxBytes.length > 255) {
108
+ throw new Error("ctx_id exceeds 255 octets \u2014 not usable with integrity algorithm 0x0001");
109
+ }
110
+ return (0, import_crypto.createHash)("sha256").update(Buffer.concat([Buffer.from([ctxBytes.length]), ctxBytes, Buffer.from([0]), fieldsPre])).digest();
111
+ }
112
+ function assemblePotRecordV08(fields, ctxId, issuerPrivateKey) {
113
+ const fieldsPre = encodeFieldsPre(fields);
114
+ const commitment = computeCommitmentSha256(fieldsPre, ctxId);
115
+ const p = Buffer.concat([fieldsPre, commitment]);
116
+ const signature = (0, import_crypto.sign)(null, p, issuerPrivateKey);
117
+ return Buffer.concat([p, signature]);
118
+ }
119
+ function generatePotRecordV08(fields, ctxId, issuerPrivateKeySeed) {
120
+ return assemblePotRecordV08(fields, ctxId, ed25519KeyFromSeed(issuerPrivateKeySeed));
121
+ }
122
+ function decodePotRecordV08(record) {
123
+ if (record.length < 2) throw new Error("record too short to read Flags");
124
+ const flags = record.readUInt8(PRE_OFFSET.HEADER + 1);
125
+ const hasHolder = (flags & 128) !== 0;
126
+ const expectedLen = hasHolder ? RECORD_SIZE_HOLDER : RECORD_SIZE_NO_HOLDER;
127
+ if (record.length !== expectedLen) {
128
+ throw new Error(`expected ${expectedLen} octets for Flags bit0=${hasHolder ? 1 : 0}, got ${record.length}`);
129
+ }
130
+ const versionTier = record.readUInt8(PRE_OFFSET.HEADER);
131
+ const version = versionTier >> 4 & 15;
132
+ const tier = versionTier & 15;
133
+ const integrityAlg = record.readUInt16BE(PRE_OFFSET.HEADER + 2);
134
+ const srcCnt = record.readUInt8(PRE_OFFSET.SRC_ERR);
135
+ const errorBoundUs = record.readUIntBE(PRE_OFFSET.SRC_ERR + 1, 3);
136
+ const timestampNs = record.readBigUInt64BE(PRE_OFFSET.TIMESTAMP);
137
+ const issuerKeyId = Buffer.from(record.subarray(PRE_OFFSET.ISSUER_KEY_ID, PRE_OFFSET.ISSUER_KEY_ID + 8));
138
+ const nonce = Buffer.from(record.subarray(PRE_OFFSET.NONCE, PRE_OFFSET.NONCE + 32));
139
+ const payloadDigest = Buffer.from(record.subarray(PRE_OFFSET.PAYLOAD_DIGEST, PRE_OFFSET.PAYLOAD_DIGEST + 32));
140
+ const preLen = hasHolder ? PRE_LEN_HOLDER : PRE_LEN_NO_HOLDER;
141
+ const holderKey = hasHolder ? Buffer.from(record.subarray(PRE_OFFSET.HOLDER_KEY, PRE_OFFSET.HOLDER_KEY + 32)) : void 0;
142
+ const fieldsPre = Buffer.from(record.subarray(0, preLen));
143
+ const commitment = Buffer.from(record.subarray(preLen, preLen + COMMITMENT_LEN));
144
+ const signature = Buffer.from(record.subarray(preLen + COMMITMENT_LEN, preLen + COMMITMENT_LEN + SIGNATURE_LEN));
145
+ const p = Buffer.from(record.subarray(0, preLen + COMMITMENT_LEN));
146
+ return {
147
+ version,
148
+ tier,
149
+ flags,
150
+ integrityAlg,
151
+ srcCnt,
152
+ errorBoundUs,
153
+ timestampNs,
154
+ issuerKeyId,
155
+ nonce,
156
+ payloadDigest,
157
+ holderKey,
158
+ commitment,
159
+ signature,
160
+ fieldsPre,
161
+ p
162
+ };
163
+ }
164
+ function verifyPotRecordV08(record, ctxId, issuerPublicKeyRaw, content) {
165
+ let decoded;
166
+ try {
167
+ decoded = decodePotRecordV08(record);
168
+ } catch (e) {
169
+ return { verdict: "rejected", reason: e instanceof Error ? e.message : String(e) };
170
+ }
171
+ if (decoded.version !== 1) {
172
+ return { verdict: "rejected", reason: `unknown version ${decoded.version}, expected 1` };
173
+ }
174
+ if (decoded.integrityAlg !== MTI_INTEGRITY_ALG_SHA256) {
175
+ return { verdict: "rejected", reason: `unimplemented integrity algorithm 0x${decoded.integrityAlg.toString(16)}` };
176
+ }
177
+ if (decoded.errorBoundUs === RESERVED_ERROR_BOUND) {
178
+ return { verdict: "rejected", reason: "Error Bound carries the reserved value 0xFFFFFF" };
179
+ }
180
+ const expectedCommitment = computeCommitmentSha256(decoded.fieldsPre, ctxId);
181
+ if (!expectedCommitment.equals(decoded.commitment)) {
182
+ return { verdict: "rejected", reason: "commitment mismatch" };
183
+ }
184
+ const publicKey = ed25519PublicKeyFromRaw(issuerPublicKeyRaw);
185
+ if (!(0, import_crypto.verify)(null, decoded.p, publicKey, decoded.signature)) {
186
+ return { verdict: "rejected", reason: "Ed25519 signature invalid" };
187
+ }
188
+ let payloadDigestMatchesContent;
189
+ if (content !== void 0) {
190
+ const recomputed = (0, import_crypto.createHash)("sha256").update(content).digest();
191
+ payloadDigestMatchesContent = recomputed.equals(decoded.payloadDigest);
192
+ if (!payloadDigestMatchesContent) {
193
+ return { verdict: "rejected", reason: "Payload Digest does not match supplied content", payloadDigestMatchesContent };
194
+ }
195
+ }
196
+ return { verdict: "intact", payloadDigestMatchesContent };
197
+ }
198
+ // Annotate the CommonJS export names for ESM import in node:
199
+ 0 && (module.exports = {
200
+ MTI_INTEGRITY_ALG_SHA256,
201
+ RECORD_SIZE_HOLDER,
202
+ RECORD_SIZE_NO_HOLDER,
203
+ RESERVED_ERROR_BOUND,
204
+ assemblePotRecordV08,
205
+ computeCommitmentSha256,
206
+ decodePotRecordV08,
207
+ ed25519KeyFromSeed,
208
+ ed25519PublicKeyFromRaw,
209
+ generatePotRecordV08,
210
+ rawPublicKeyOf,
211
+ verifyPotRecordV08
212
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helm-protocol/ttt-mcp",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
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": {
@@ -11,7 +11,7 @@
11
11
  "README.md"
12
12
  ],
13
13
  "scripts": {
14
- "build": "esbuild index.ts tools.ts telemetry.ts auth.ts server.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 --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",
@@ -20,16 +20,21 @@
20
20
  "jest": {
21
21
  "preset": "ts-jest",
22
22
  "testEnvironment": "node",
23
- "testMatch": ["**/__tests__/**/*.test.ts"],
23
+ "testMatch": [
24
+ "**/__tests__/**/*.test.ts"
25
+ ],
24
26
  "moduleNameMapper": {
25
27
  "^openttt$": "<rootDir>/node_modules/openttt/dist/index.js"
26
28
  },
27
29
  "transform": {
28
- "^.+\\.tsx?$": ["ts-jest", {
29
- "tsconfig": {
30
- "module": "CommonJS"
30
+ "^.+\\.tsx?$": [
31
+ "ts-jest",
32
+ {
33
+ "tsconfig": {
34
+ "module": "CommonJS"
35
+ }
31
36
  }
32
- }]
37
+ ]
33
38
  }
34
39
  },
35
40
  "keywords": [
@@ -54,7 +59,7 @@
54
59
  "dependencies": {
55
60
  "@modelcontextprotocol/sdk": "^1.27.1",
56
61
  "ioredis": "^5.11.0",
57
- "openttt": "^0.2.13",
62
+ "openttt": "^0.3.1",
58
63
  "zod": "^3.25.0"
59
64
  },
60
65
  "devDependencies": {