openttt 0.2.12 → 0.2.13
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 +21 -1
- package/dist/adaptive_switch.d.ts +41 -0
- package/dist/adaptive_switch.js +108 -0
- package/dist/http_client.d.ts +98 -0
- package/dist/http_client.js +257 -0
- package/dist/index.d.ts +3 -4
- package/dist/index.js +6 -4
- package/dist/integrity_client.d.ts +41 -0
- package/dist/integrity_client.js +122 -0
- package/dist/osnma_source.d.ts +82 -0
- package/dist/osnma_source.js +169 -0
- package/dist/pot_frame.d.ts +59 -0
- package/dist/pot_frame.js +130 -0
- package/dist/pot_verifier.d.ts +56 -0
- package/dist/pot_verifier.js +183 -0
- package/dist/tls_binding.d.ts +52 -0
- package/dist/tls_binding.js +86 -0
- package/dist/v4_hook.d.ts +7 -2
- package/package.json +12 -3
package/README.md
CHANGED
|
@@ -231,9 +231,29 @@ try {
|
|
|
231
231
|
|
|
232
232
|
---
|
|
233
233
|
|
|
234
|
+
## Pricing
|
|
235
|
+
|
|
236
|
+
| Tier | Price | Quota | Use Case |
|
|
237
|
+
|---|---|---|---|
|
|
238
|
+
| **FREE** | $0 | 100 calls/day | Non-commercial, research, personal bots (revenue < $10K/yr) |
|
|
239
|
+
| **BOT** | $199/mo | 5M calls · 1K req/s | MEV bots, arbitrage bots |
|
|
240
|
+
| **DEX/LP** | $499/mo | 20M calls + overage | DEX protocols, liquidity providers |
|
|
241
|
+
| **FUND** | $2,000+/mo | Unlimited + MiFIR Art.22c audit log | Hedge funds, OTC desks, prop trading |
|
|
242
|
+
|
|
243
|
+
API key & commercial licensing: heime.jorgen@proton.me
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
234
247
|
## License
|
|
235
248
|
|
|
236
|
-
|
|
249
|
+
BSL-1.1 — free for non-commercial use.
|
|
250
|
+
|
|
251
|
+
**Commercial use** (production bots, hedge funds, prop desks) requires a license.
|
|
252
|
+
→ [Pricing](https://github.com/Helm-Protocol/openttt-mcp#pricing)
|
|
253
|
+
|
|
254
|
+
Change Date: 2029-05-28 → Apache 2.0
|
|
255
|
+
|
|
256
|
+
Full license text: [LICENSE](LICENSE) — Copyright 2026 Helm Protocol.
|
|
237
257
|
|
|
238
258
|
---
|
|
239
259
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export declare enum AdaptiveMode {
|
|
2
|
+
TURBO = "TURBO",// 50ms — Valid sequence, low latency
|
|
3
|
+
FULL = "FULL"
|
|
4
|
+
}
|
|
5
|
+
export interface TTTRecord {
|
|
6
|
+
time: number;
|
|
7
|
+
txOrder: string[];
|
|
8
|
+
grgPayload: Uint8Array[];
|
|
9
|
+
}
|
|
10
|
+
export interface Block {
|
|
11
|
+
timestamp: number;
|
|
12
|
+
txs: string[];
|
|
13
|
+
data: Uint8Array;
|
|
14
|
+
}
|
|
15
|
+
/** Tier-based dynamic tolerance (ms) */
|
|
16
|
+
export declare const TIER_TOLERANCE_MS: Record<string, number>;
|
|
17
|
+
export declare class AdaptiveSwitch {
|
|
18
|
+
private windowSize;
|
|
19
|
+
private turboEntryThreshold;
|
|
20
|
+
private turboMaintainThreshold;
|
|
21
|
+
private history;
|
|
22
|
+
private currentMode;
|
|
23
|
+
private minBlocks;
|
|
24
|
+
private penaltyCooldown;
|
|
25
|
+
private consecutiveFailures;
|
|
26
|
+
private tolerance;
|
|
27
|
+
constructor(options?: {
|
|
28
|
+
tolerance?: number;
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* Client-side TTT check: timestamp ordering + time delta only.
|
|
32
|
+
* GRG integrity verification is delegated to the server (IntegrityClient).
|
|
33
|
+
*/
|
|
34
|
+
verifyBlock(block: Block, tttRecord: TTTRecord, _chainId: number, _poolAddress: string, tier?: string): AdaptiveMode;
|
|
35
|
+
getFeeDiscount(): number;
|
|
36
|
+
getCurrentMode(): AdaptiveMode;
|
|
37
|
+
reset(): void;
|
|
38
|
+
serialize(): string;
|
|
39
|
+
static deserialize(json: string): AdaptiveSwitch;
|
|
40
|
+
private compareTransactionOrder;
|
|
41
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// sdk/src/adaptive_switch.ts — Adaptive Mode Switcher (public SDK stub)
|
|
3
|
+
// GRG integrity check runs server-side (helm private repo).
|
|
4
|
+
// This module exports types, enums, and the client-side AdaptiveSwitch
|
|
5
|
+
// (timestamp + ordering check only; integrity check delegated to server).
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.AdaptiveSwitch = exports.TIER_TOLERANCE_MS = exports.AdaptiveMode = void 0;
|
|
8
|
+
const logger_1 = require("./logger");
|
|
9
|
+
var AdaptiveMode;
|
|
10
|
+
(function (AdaptiveMode) {
|
|
11
|
+
AdaptiveMode["TURBO"] = "TURBO";
|
|
12
|
+
AdaptiveMode["FULL"] = "FULL";
|
|
13
|
+
})(AdaptiveMode || (exports.AdaptiveMode = AdaptiveMode = {}));
|
|
14
|
+
/** Tier-based dynamic tolerance (ms) */
|
|
15
|
+
exports.TIER_TOLERANCE_MS = {
|
|
16
|
+
T0_epoch: 2000,
|
|
17
|
+
T1_block: 200,
|
|
18
|
+
T2_slot: 500,
|
|
19
|
+
T3_micro: 10,
|
|
20
|
+
};
|
|
21
|
+
class AdaptiveSwitch {
|
|
22
|
+
windowSize = 20;
|
|
23
|
+
turboEntryThreshold = 0.95;
|
|
24
|
+
turboMaintainThreshold = 0.85;
|
|
25
|
+
history = [];
|
|
26
|
+
currentMode = AdaptiveMode.FULL;
|
|
27
|
+
minBlocks = 20;
|
|
28
|
+
penaltyCooldown = 0;
|
|
29
|
+
consecutiveFailures = 0;
|
|
30
|
+
tolerance;
|
|
31
|
+
constructor(options) {
|
|
32
|
+
this.tolerance = options?.tolerance ?? 100;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Client-side TTT check: timestamp ordering + time delta only.
|
|
36
|
+
* GRG integrity verification is delegated to the server (IntegrityClient).
|
|
37
|
+
*/
|
|
38
|
+
verifyBlock(block, tttRecord, _chainId, _poolAddress, tier) {
|
|
39
|
+
const orderMatch = this.compareTransactionOrder(block.txs, tttRecord.txOrder);
|
|
40
|
+
const tolerance = tier ? (exports.TIER_TOLERANCE_MS[tier] ?? this.tolerance) : this.tolerance;
|
|
41
|
+
const timeMatch = Math.abs(block.timestamp - tttRecord.time) < tolerance;
|
|
42
|
+
const sequenceOk = orderMatch && timeMatch;
|
|
43
|
+
this.history.push(sequenceOk);
|
|
44
|
+
if (this.history.length > this.windowSize)
|
|
45
|
+
this.history.shift();
|
|
46
|
+
if (this.penaltyCooldown > 0)
|
|
47
|
+
this.penaltyCooldown--;
|
|
48
|
+
const matchCount = this.history.filter(h => h).length;
|
|
49
|
+
const matchRate = this.history.length > 0 ? matchCount / this.history.length : 0;
|
|
50
|
+
const effectiveThreshold = this.currentMode === AdaptiveMode.TURBO
|
|
51
|
+
? this.turboMaintainThreshold
|
|
52
|
+
: this.turboEntryThreshold;
|
|
53
|
+
if (this.history.length >= this.minBlocks && matchRate >= effectiveThreshold && this.penaltyCooldown === 0) {
|
|
54
|
+
if (this.currentMode === AdaptiveMode.FULL) {
|
|
55
|
+
logger_1.logger.info(`[AdaptiveSwitch] Switching to TURBO (rate: ${(matchRate * 100).toFixed(1)}%)`);
|
|
56
|
+
}
|
|
57
|
+
this.currentMode = AdaptiveMode.TURBO;
|
|
58
|
+
this.consecutiveFailures = 0;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
if (this.currentMode === AdaptiveMode.TURBO) {
|
|
62
|
+
logger_1.logger.warn(`[AdaptiveSwitch] Switching to FULL (rate: ${(matchRate * 100).toFixed(1)}%)`);
|
|
63
|
+
}
|
|
64
|
+
this.currentMode = AdaptiveMode.FULL;
|
|
65
|
+
}
|
|
66
|
+
return this.currentMode;
|
|
67
|
+
}
|
|
68
|
+
getFeeDiscount() {
|
|
69
|
+
return this.currentMode === AdaptiveMode.TURBO ? 0.2 : 0.0;
|
|
70
|
+
}
|
|
71
|
+
getCurrentMode() {
|
|
72
|
+
return this.currentMode;
|
|
73
|
+
}
|
|
74
|
+
reset() {
|
|
75
|
+
this.history = [];
|
|
76
|
+
this.currentMode = AdaptiveMode.FULL;
|
|
77
|
+
this.penaltyCooldown = 0;
|
|
78
|
+
this.consecutiveFailures = 0;
|
|
79
|
+
}
|
|
80
|
+
serialize() {
|
|
81
|
+
return JSON.stringify({
|
|
82
|
+
history: this.history,
|
|
83
|
+
currentMode: this.currentMode,
|
|
84
|
+
consecutiveFailures: this.consecutiveFailures,
|
|
85
|
+
penaltyCooldown: this.penaltyCooldown,
|
|
86
|
+
tolerance: this.tolerance,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
static deserialize(json) {
|
|
90
|
+
const data = JSON.parse(json);
|
|
91
|
+
const instance = new AdaptiveSwitch({ tolerance: data.tolerance ?? 100 });
|
|
92
|
+
instance.history = data.history;
|
|
93
|
+
instance.currentMode = data.currentMode;
|
|
94
|
+
instance.consecutiveFailures = data.consecutiveFailures;
|
|
95
|
+
instance.penaltyCooldown = data.penaltyCooldown;
|
|
96
|
+
return instance;
|
|
97
|
+
}
|
|
98
|
+
compareTransactionOrder(blockTxs, expectedOrder) {
|
|
99
|
+
if (blockTxs.length !== expectedOrder.length)
|
|
100
|
+
return false;
|
|
101
|
+
for (let i = 0; i < blockTxs.length; i++) {
|
|
102
|
+
if (blockTxs[i] !== expectedOrder[i])
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
exports.AdaptiveSwitch = AdaptiveSwitch;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TTTClient.httpOnly() — Zero-friction Proof of Time
|
|
3
|
+
* No ETH, no signer, no on-chain. Just verified time.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* const client = TTTClient.httpOnly();
|
|
7
|
+
* const pot = await client.generatePoT();
|
|
8
|
+
* console.log(pot.timestamp, pot.confidence);
|
|
9
|
+
*
|
|
10
|
+
* Time sources: NIST, Apple, Google, Cloudflare HTTPS Date headers.
|
|
11
|
+
* HMAC-SHA256 signature — no ethers.js dependency required.
|
|
12
|
+
*/
|
|
13
|
+
import { ProofOfTime } from "./types";
|
|
14
|
+
export interface HttpPoT {
|
|
15
|
+
/** Synthesized timestamp in Unix nanoseconds (bigint). */
|
|
16
|
+
timestamp: bigint;
|
|
17
|
+
/** Fraction of configured sources that responded (0.0–1.0). */
|
|
18
|
+
confidence: number;
|
|
19
|
+
/** Lowest NTP stratum observed across sources (2 for HTTPS Date headers). */
|
|
20
|
+
stratum: number;
|
|
21
|
+
/** Number of sources that successfully responded. */
|
|
22
|
+
sources: number;
|
|
23
|
+
/** Per-source readings used to compute the median. */
|
|
24
|
+
sourceReadings: {
|
|
25
|
+
source: string;
|
|
26
|
+
timestamp: bigint;
|
|
27
|
+
uncertainty: number;
|
|
28
|
+
stratum?: number;
|
|
29
|
+
}[];
|
|
30
|
+
/** Replay-protection nonce (hex). */
|
|
31
|
+
nonce: string;
|
|
32
|
+
/** Expiry timestamp in Unix milliseconds (bigint). */
|
|
33
|
+
expiresAt: bigint;
|
|
34
|
+
/** HMAC-SHA256 over canonical fields, hex-encoded. */
|
|
35
|
+
hmac: string;
|
|
36
|
+
}
|
|
37
|
+
export interface HttpPoTVerifyResult {
|
|
38
|
+
valid: boolean;
|
|
39
|
+
reason?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface HttpOnlyClientOptions {
|
|
42
|
+
/**
|
|
43
|
+
* Override HMAC secret for non-sandbox usage.
|
|
44
|
+
* Defaults to a fixed sandbox key — sufficient for local verification only.
|
|
45
|
+
*/
|
|
46
|
+
hmacSecret?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Per-source request timeout in ms. Default: 3000.
|
|
49
|
+
*/
|
|
50
|
+
timeoutMs?: number;
|
|
51
|
+
/**
|
|
52
|
+
* PoT validity window in seconds. Default: 60.
|
|
53
|
+
*/
|
|
54
|
+
expirySeconds?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Maximum divergence allowed between sources in nanoseconds.
|
|
57
|
+
* Default: 2_000_000_000n (2 seconds — lenient for HTTPS Date 1s resolution).
|
|
58
|
+
*/
|
|
59
|
+
toleranceNs?: bigint;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* HttpOnlyClient — zero-dependency Proof of Time over HTTPS.
|
|
63
|
+
*
|
|
64
|
+
* Fetches time from NIST, Apple, Google, and Cloudflare HTTPS endpoints,
|
|
65
|
+
* computes the median, and returns a PoT with HMAC integrity protection.
|
|
66
|
+
*
|
|
67
|
+
* No ETH, no signer, no on-chain interaction required.
|
|
68
|
+
*/
|
|
69
|
+
export declare class HttpOnlyClient {
|
|
70
|
+
private readonly hmacSecret;
|
|
71
|
+
private readonly timeoutMs;
|
|
72
|
+
private readonly expirySeconds;
|
|
73
|
+
private readonly toleranceNs;
|
|
74
|
+
private readonly usedNonces;
|
|
75
|
+
private readonly NONCE_TTL_MS;
|
|
76
|
+
private readonly MAX_NONCE_CACHE;
|
|
77
|
+
constructor(options?: HttpOnlyClientOptions);
|
|
78
|
+
/**
|
|
79
|
+
* Fetch time from all four HTTPS sources, compute median, return PoT.
|
|
80
|
+
* Requires at least 1 source to succeed.
|
|
81
|
+
*/
|
|
82
|
+
generatePoT(): Promise<HttpPoT>;
|
|
83
|
+
/**
|
|
84
|
+
* Verify an HttpPoT:
|
|
85
|
+
* - HMAC integrity check
|
|
86
|
+
* - Expiry check
|
|
87
|
+
* - Nonce replay protection
|
|
88
|
+
* - Source divergence tolerance check
|
|
89
|
+
*
|
|
90
|
+
* No on-chain interaction. Pure local verification.
|
|
91
|
+
*/
|
|
92
|
+
verifyPoT(pot: HttpPoT): HttpPoTVerifyResult;
|
|
93
|
+
/**
|
|
94
|
+
* Convert HttpPoT to the standard ProofOfTime shape used by the rest of the SDK.
|
|
95
|
+
* The issuerSignature field is omitted (no on-chain signer in httpOnly mode).
|
|
96
|
+
*/
|
|
97
|
+
static toProofOfTime(pot: HttpPoT): ProofOfTime;
|
|
98
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* TTTClient.httpOnly() — Zero-friction Proof of Time
|
|
4
|
+
* No ETH, no signer, no on-chain. Just verified time.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* const client = TTTClient.httpOnly();
|
|
8
|
+
* const pot = await client.generatePoT();
|
|
9
|
+
* console.log(pot.timestamp, pot.confidence);
|
|
10
|
+
*
|
|
11
|
+
* Time sources: NIST, Apple, Google, Cloudflare HTTPS Date headers.
|
|
12
|
+
* HMAC-SHA256 signature — no ethers.js dependency required.
|
|
13
|
+
*/
|
|
14
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
17
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
19
|
+
}
|
|
20
|
+
Object.defineProperty(o, k2, desc);
|
|
21
|
+
}) : (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
o[k2] = m[k];
|
|
24
|
+
}));
|
|
25
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
26
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
27
|
+
}) : function(o, v) {
|
|
28
|
+
o["default"] = v;
|
|
29
|
+
});
|
|
30
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
31
|
+
var ownKeys = function(o) {
|
|
32
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
33
|
+
var ar = [];
|
|
34
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
35
|
+
return ar;
|
|
36
|
+
};
|
|
37
|
+
return ownKeys(o);
|
|
38
|
+
};
|
|
39
|
+
return function (mod) {
|
|
40
|
+
if (mod && mod.__esModule) return mod;
|
|
41
|
+
var result = {};
|
|
42
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
43
|
+
__setModuleDefault(result, mod);
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
})();
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
exports.HttpOnlyClient = void 0;
|
|
49
|
+
const crypto = __importStar(require("crypto"));
|
|
50
|
+
const https = __importStar(require("https"));
|
|
51
|
+
const SOURCES = [
|
|
52
|
+
{ name: "nist", url: "https://time.nist.gov/" },
|
|
53
|
+
{ name: "apple", url: "https://time.apple.com/" },
|
|
54
|
+
{ name: "google", url: "https://time.google.com/" },
|
|
55
|
+
{ name: "cloudflare", url: "https://time.cloudflare.com/" },
|
|
56
|
+
];
|
|
57
|
+
function fetchHttpsDate(name, url, timeoutMs = 3000) {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const t1 = BigInt(Date.now()) * 1000000n;
|
|
60
|
+
const timer = setTimeout(() => {
|
|
61
|
+
req.destroy();
|
|
62
|
+
reject(new Error(`[httpOnly] Timeout: ${name} (${timeoutMs}ms)`));
|
|
63
|
+
}, timeoutMs);
|
|
64
|
+
const req = https.request(url, { method: "HEAD" }, (res) => {
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
res.resume(); // drain so socket is freed
|
|
67
|
+
const t4 = BigInt(Date.now()) * 1000000n;
|
|
68
|
+
const dateHeader = res.headers["date"];
|
|
69
|
+
if (!dateHeader) {
|
|
70
|
+
reject(new Error(`[httpOnly] No Date header from ${name}`));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const parsed = new Date(dateHeader).getTime();
|
|
74
|
+
if (isNaN(parsed)) {
|
|
75
|
+
reject(new Error(`[httpOnly] Invalid Date header from ${name}: ${dateHeader}`));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const serverNs = BigInt(parsed) * 1000000n;
|
|
79
|
+
const rttNs = t4 - t1;
|
|
80
|
+
// Offset-corrected: server time + half RTT
|
|
81
|
+
const corrected = serverNs + rttNs / 2n;
|
|
82
|
+
const rttMs = Number(rttNs) / 1_000_000;
|
|
83
|
+
resolve({
|
|
84
|
+
source: name,
|
|
85
|
+
timestamp: corrected,
|
|
86
|
+
uncertainty: rttMs / 2 + 500, // 500ms base — HTTP Date has 1s resolution
|
|
87
|
+
stratum: 2,
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
req.on("error", (err) => {
|
|
91
|
+
clearTimeout(timer);
|
|
92
|
+
reject(new Error(`[httpOnly] Request error for ${name}: ${err.message}`));
|
|
93
|
+
});
|
|
94
|
+
req.end();
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// HMAC helpers — default sandbox key derived from fixed chain+address strings
|
|
99
|
+
// HMAC key is public by design — context binding, not secrecy.
|
|
100
|
+
// The sandbox key below is a fixed string for local-only verification.
|
|
101
|
+
// In production, the HMAC key is derived from (chainId, poolAddress) and
|
|
102
|
+
// is deterministically recomputable by any party. The security property is
|
|
103
|
+
// tamper detection (integrity), not confidentiality.
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
const SANDBOX_HMAC_SECRET = "openttt-sandbox:chainId=0:address=0x0000000000000000000000000000000000000000";
|
|
106
|
+
function computeHmac(timestamp, nonce, expiresAt, sources, secret = SANDBOX_HMAC_SECRET) {
|
|
107
|
+
// Canonical message: deterministic field concatenation
|
|
108
|
+
const msg = `${timestamp.toString()}:${nonce}:${expiresAt.toString()}:${sources}`;
|
|
109
|
+
return crypto.createHmac("sha256", secret).update(msg).digest("hex");
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* HttpOnlyClient — zero-dependency Proof of Time over HTTPS.
|
|
113
|
+
*
|
|
114
|
+
* Fetches time from NIST, Apple, Google, and Cloudflare HTTPS endpoints,
|
|
115
|
+
* computes the median, and returns a PoT with HMAC integrity protection.
|
|
116
|
+
*
|
|
117
|
+
* No ETH, no signer, no on-chain interaction required.
|
|
118
|
+
*/
|
|
119
|
+
class HttpOnlyClient {
|
|
120
|
+
hmacSecret;
|
|
121
|
+
timeoutMs;
|
|
122
|
+
expirySeconds;
|
|
123
|
+
toleranceNs;
|
|
124
|
+
usedNonces = new Map();
|
|
125
|
+
NONCE_TTL_MS = 300_000; // 5 min
|
|
126
|
+
MAX_NONCE_CACHE = 10_000;
|
|
127
|
+
constructor(options = {}) {
|
|
128
|
+
this.hmacSecret = options.hmacSecret ?? SANDBOX_HMAC_SECRET;
|
|
129
|
+
this.timeoutMs = options.timeoutMs ?? 3000;
|
|
130
|
+
this.expirySeconds = options.expirySeconds ?? 60;
|
|
131
|
+
this.toleranceNs = options.toleranceNs ?? 2000000000n; // 2 s
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Fetch time from all four HTTPS sources, compute median, return PoT.
|
|
135
|
+
* Requires at least 1 source to succeed.
|
|
136
|
+
*/
|
|
137
|
+
async generatePoT() {
|
|
138
|
+
const results = await Promise.allSettled(SOURCES.map((s) => fetchHttpsDate(s.name, s.url, this.timeoutMs)));
|
|
139
|
+
const readings = [];
|
|
140
|
+
for (const r of results) {
|
|
141
|
+
if (r.status === "fulfilled")
|
|
142
|
+
readings.push(r.value);
|
|
143
|
+
}
|
|
144
|
+
if (readings.length === 0) {
|
|
145
|
+
throw new Error("[httpOnly] All HTTPS time sources failed. Check network connectivity.");
|
|
146
|
+
}
|
|
147
|
+
// Sort by timestamp for median selection
|
|
148
|
+
readings.sort((a, b) => (a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0));
|
|
149
|
+
let finalTs;
|
|
150
|
+
let finalUnc;
|
|
151
|
+
let finalStratum;
|
|
152
|
+
if (readings.length === 1) {
|
|
153
|
+
finalTs = readings[0].timestamp;
|
|
154
|
+
finalUnc = readings[0].uncertainty;
|
|
155
|
+
finalStratum = readings[0].stratum;
|
|
156
|
+
}
|
|
157
|
+
else if (readings.length === 2) {
|
|
158
|
+
finalTs = (readings[0].timestamp + readings[1].timestamp) / 2n;
|
|
159
|
+
finalUnc = (readings[0].uncertainty + readings[1].uncertainty) / 2;
|
|
160
|
+
finalStratum = Math.min(readings[0].stratum, readings[1].stratum);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
const mid = Math.floor(readings.length / 2);
|
|
164
|
+
finalTs = readings[mid].timestamp;
|
|
165
|
+
finalUnc = readings[mid].uncertainty;
|
|
166
|
+
finalStratum = readings[mid].stratum;
|
|
167
|
+
}
|
|
168
|
+
const nonce = crypto.randomBytes(16).toString("hex");
|
|
169
|
+
const expiresAt = BigInt(Date.now()) + BigInt(this.expirySeconds * 1000);
|
|
170
|
+
const sourceReadings = readings.map((r) => ({
|
|
171
|
+
source: r.source,
|
|
172
|
+
timestamp: r.timestamp,
|
|
173
|
+
uncertainty: r.uncertainty,
|
|
174
|
+
stratum: r.stratum,
|
|
175
|
+
}));
|
|
176
|
+
const hmac = computeHmac(finalTs, nonce, expiresAt, readings.length, this.hmacSecret);
|
|
177
|
+
return {
|
|
178
|
+
timestamp: finalTs,
|
|
179
|
+
confidence: readings.length / SOURCES.length,
|
|
180
|
+
stratum: finalStratum,
|
|
181
|
+
sources: readings.length,
|
|
182
|
+
sourceReadings,
|
|
183
|
+
nonce,
|
|
184
|
+
expiresAt,
|
|
185
|
+
hmac,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Verify an HttpPoT:
|
|
190
|
+
* - HMAC integrity check
|
|
191
|
+
* - Expiry check
|
|
192
|
+
* - Nonce replay protection
|
|
193
|
+
* - Source divergence tolerance check
|
|
194
|
+
*
|
|
195
|
+
* No on-chain interaction. Pure local verification.
|
|
196
|
+
*/
|
|
197
|
+
verifyPoT(pot) {
|
|
198
|
+
// 1. Expiry
|
|
199
|
+
if (BigInt(Date.now()) > pot.expiresAt) {
|
|
200
|
+
return { valid: false, reason: "PoT expired" };
|
|
201
|
+
}
|
|
202
|
+
// 2. HMAC integrity
|
|
203
|
+
const expected = computeHmac(pot.timestamp, pot.nonce, pot.expiresAt, pot.sources, this.hmacSecret);
|
|
204
|
+
if (!crypto.timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(pot.hmac, "hex"))) {
|
|
205
|
+
return { valid: false, reason: "HMAC mismatch — PoT may have been tampered" };
|
|
206
|
+
}
|
|
207
|
+
// 3. Nonce replay (bounded cache + TTL)
|
|
208
|
+
const now = Date.now();
|
|
209
|
+
if (this.usedNonces.size > this.MAX_NONCE_CACHE / 2) {
|
|
210
|
+
for (const [k, ts] of this.usedNonces) {
|
|
211
|
+
if (now - ts > this.NONCE_TTL_MS)
|
|
212
|
+
this.usedNonces.delete(k);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (this.usedNonces.has(pot.nonce)) {
|
|
216
|
+
return { valid: false, reason: "Duplicate nonce — replay detected" };
|
|
217
|
+
}
|
|
218
|
+
if (this.usedNonces.size >= this.MAX_NONCE_CACHE) {
|
|
219
|
+
const oldest = this.usedNonces.keys().next().value;
|
|
220
|
+
if (oldest !== undefined)
|
|
221
|
+
this.usedNonces.delete(oldest);
|
|
222
|
+
}
|
|
223
|
+
this.usedNonces.set(pot.nonce, now);
|
|
224
|
+
// 4. Source divergence
|
|
225
|
+
for (const reading of pot.sourceReadings) {
|
|
226
|
+
const diff = reading.timestamp > pot.timestamp
|
|
227
|
+
? reading.timestamp - pot.timestamp
|
|
228
|
+
: pot.timestamp - reading.timestamp;
|
|
229
|
+
if (diff > this.toleranceNs) {
|
|
230
|
+
return {
|
|
231
|
+
valid: false,
|
|
232
|
+
reason: `Source ${reading.source} diverges by ${diff}ns (tolerance: ${this.toleranceNs}ns)`,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return { valid: true };
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Convert HttpPoT to the standard ProofOfTime shape used by the rest of the SDK.
|
|
240
|
+
* The issuerSignature field is omitted (no on-chain signer in httpOnly mode).
|
|
241
|
+
*/
|
|
242
|
+
static toProofOfTime(pot) {
|
|
243
|
+
return {
|
|
244
|
+
timestamp: pot.timestamp,
|
|
245
|
+
uncertainty: pot.sourceReadings.length > 0
|
|
246
|
+
? pot.sourceReadings.reduce((sum, r) => sum + r.uncertainty, 0) / pot.sourceReadings.length
|
|
247
|
+
: 500,
|
|
248
|
+
sources: pot.sources,
|
|
249
|
+
stratum: pot.stratum,
|
|
250
|
+
confidence: pot.confidence,
|
|
251
|
+
sourceReadings: pot.sourceReadings,
|
|
252
|
+
nonce: pot.nonce,
|
|
253
|
+
expiresAt: pot.expiresAt,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
exports.HttpOnlyClient = HttpOnlyClient;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
export * from "./evm_connector";
|
|
2
|
-
export * from "./x402_enforcer";
|
|
3
2
|
export * from "./adaptive_switch";
|
|
4
|
-
export * from "./protocol_fee";
|
|
5
3
|
export * from "./pool_registry";
|
|
6
4
|
export * from "./v4_hook";
|
|
7
5
|
export * from "./logger";
|
|
8
6
|
export * from "./types";
|
|
9
7
|
export * from "./http_client";
|
|
10
8
|
export * from "./time_synthesis";
|
|
11
|
-
export * from "./dynamic_fee";
|
|
12
9
|
export * from "./signer";
|
|
13
10
|
export * from "./networks";
|
|
14
11
|
export * from "./errors";
|
|
15
12
|
export * from "./pot_signer";
|
|
16
13
|
export * from "./ct_log";
|
|
17
14
|
export * from "./trust_store";
|
|
18
|
-
export * from "./revenue_tiers";
|
|
19
15
|
export * from "./integrity_client";
|
|
20
16
|
export * from "./osnma_source";
|
|
17
|
+
export * from "./tls_binding";
|
|
18
|
+
export * from "./pot_frame";
|
|
19
|
+
export * from "./pot_verifier";
|
package/dist/index.js
CHANGED
|
@@ -19,11 +19,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
19
19
|
// grg_forward, grg_inverse, grg_pipeline, golay, reed_solomon
|
|
20
20
|
// auto_mint (GRG 의존)
|
|
21
21
|
// 위 모듈은 서버 코드에서 직접 경로로 import할 것.
|
|
22
|
+
// 유료화 모듈 (private helm repo로 이동):
|
|
23
|
+
// protocol_fee, revenue_tiers, dynamic_fee, x402_enforcer
|
|
22
24
|
__exportStar(require("./evm_connector"), exports);
|
|
23
|
-
__exportStar(require("./x402_enforcer"), exports);
|
|
24
25
|
__exportStar(require("./adaptive_switch"), exports);
|
|
25
26
|
// ttt_builder omitted — server-internal only
|
|
26
|
-
__exportStar(require("./protocol_fee"), exports);
|
|
27
27
|
__exportStar(require("./pool_registry"), exports);
|
|
28
28
|
__exportStar(require("./v4_hook"), exports);
|
|
29
29
|
__exportStar(require("./logger"), exports);
|
|
@@ -31,13 +31,15 @@ __exportStar(require("./types"), exports);
|
|
|
31
31
|
// ttt_client omitted — requires auto_mint (server-internal, not in public SDK)
|
|
32
32
|
__exportStar(require("./http_client"), exports);
|
|
33
33
|
__exportStar(require("./time_synthesis"), exports);
|
|
34
|
-
__exportStar(require("./dynamic_fee"), exports);
|
|
35
34
|
__exportStar(require("./signer"), exports);
|
|
36
35
|
__exportStar(require("./networks"), exports);
|
|
37
36
|
__exportStar(require("./errors"), exports);
|
|
38
37
|
__exportStar(require("./pot_signer"), exports);
|
|
39
38
|
__exportStar(require("./ct_log"), exports);
|
|
40
39
|
__exportStar(require("./trust_store"), exports);
|
|
41
|
-
__exportStar(require("./revenue_tiers"), exports);
|
|
42
40
|
__exportStar(require("./integrity_client"), exports);
|
|
43
41
|
__exportStar(require("./osnma_source"), exports);
|
|
42
|
+
// TTTPS -02: TLS binding_key implementation (draft-helmprotocol-tttps-02 Section 7.1)
|
|
43
|
+
__exportStar(require("./tls_binding"), exports);
|
|
44
|
+
__exportStar(require("./pot_frame"), exports);
|
|
45
|
+
__exportStar(require("./pot_verifier"), exports);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integrity Client — replaces local integrity computation with server-side API call.
|
|
3
|
+
* Core pipeline source code stays in Helm private repo. Only API calls go through npm SDK.
|
|
4
|
+
*
|
|
5
|
+
* Drop-in replacement interface for local encode() and verify() operations.
|
|
6
|
+
*/
|
|
7
|
+
export interface IntegrityEncodeResult {
|
|
8
|
+
/** Hex-encoded serialized shards (joined as JSON array of hex strings) */
|
|
9
|
+
shards: string[];
|
|
10
|
+
}
|
|
11
|
+
export interface IntegrityVerifyResult {
|
|
12
|
+
valid: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare class IntegrityClient {
|
|
15
|
+
private baseUrl;
|
|
16
|
+
private timeoutMs;
|
|
17
|
+
private apiKey;
|
|
18
|
+
constructor(baseUrl?: string, options?: {
|
|
19
|
+
timeoutMs?: number;
|
|
20
|
+
apiKey?: string;
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* Forward pass: encode data through integrity pipeline (server-side).
|
|
24
|
+
*
|
|
25
|
+
* @returns Array of Uint8Array shards
|
|
26
|
+
*/
|
|
27
|
+
encode(data: Uint8Array, chainId: number, poolAddress: string): Promise<Uint8Array[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Verify: check data integrity (server-side).
|
|
30
|
+
*
|
|
31
|
+
* @returns boolean — true if data matches the original shards
|
|
32
|
+
*/
|
|
33
|
+
verify(data: Uint8Array, originalShards: Uint8Array[], chainId: number, poolAddress: string): Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Health check — ping the integrity API server.
|
|
36
|
+
* Returns true if reachable within timeoutMs.
|
|
37
|
+
*/
|
|
38
|
+
isReachable(): Promise<boolean>;
|
|
39
|
+
}
|
|
40
|
+
export declare function getDefaultIntegrityClient(): IntegrityClient;
|
|
41
|
+
export declare function setDefaultIntegrityClient(client: IntegrityClient): void;
|