openttt 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auto_mint.d.ts +1 -0
- package/dist/auto_mint.js +9 -0
- package/dist/grg_forward.d.ts +7 -2
- package/dist/grg_forward.js +21 -6
- package/dist/grg_inverse.d.ts +2 -2
- package/dist/grg_inverse.js +10 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/pot_signer.d.ts +29 -0
- package/dist/pot_signer.js +73 -0
- package/dist/time_synthesis.d.ts +6 -0
- package/dist/time_synthesis.js +71 -14
- package/dist/types.d.ts +6 -1
- package/package.json +1 -1
package/dist/auto_mint.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export declare class AutoMintEngine {
|
|
|
19
19
|
private cachedSigner;
|
|
20
20
|
private consecutiveFailures;
|
|
21
21
|
private maxConsecutiveFailures;
|
|
22
|
+
private potSigner;
|
|
22
23
|
constructor(config: AutoMintConfig);
|
|
23
24
|
getEvmConnector(): EVMConnector;
|
|
24
25
|
setOnMint(callback: (result: MintResult) => void): void;
|
package/dist/auto_mint.js
CHANGED
|
@@ -7,6 +7,7 @@ const time_synthesis_1 = require("./time_synthesis");
|
|
|
7
7
|
const dynamic_fee_1 = require("./dynamic_fee");
|
|
8
8
|
const evm_connector_1 = require("./evm_connector");
|
|
9
9
|
const protocol_fee_1 = require("./protocol_fee");
|
|
10
|
+
const pot_signer_1 = require("./pot_signer");
|
|
10
11
|
const types_1 = require("./types");
|
|
11
12
|
const logger_1 = require("./logger");
|
|
12
13
|
const errors_1 = require("./errors");
|
|
@@ -29,6 +30,7 @@ class AutoMintEngine {
|
|
|
29
30
|
cachedSigner = null;
|
|
30
31
|
consecutiveFailures = 0;
|
|
31
32
|
maxConsecutiveFailures = 5;
|
|
33
|
+
potSigner = null;
|
|
32
34
|
constructor(config) {
|
|
33
35
|
this.config = config;
|
|
34
36
|
this.timeSynthesis = new time_synthesis_1.TimeSynthesis({ sources: config.timeSources });
|
|
@@ -40,6 +42,8 @@ class AutoMintEngine {
|
|
|
40
42
|
if (config.signer) {
|
|
41
43
|
this.cachedSigner = config.signer;
|
|
42
44
|
}
|
|
45
|
+
// Initialize Ed25519 PoT signer for non-repudiation
|
|
46
|
+
this.potSigner = new pot_signer_1.PotSigner();
|
|
43
47
|
}
|
|
44
48
|
getEvmConnector() {
|
|
45
49
|
return this.evmConnector;
|
|
@@ -162,6 +166,11 @@ class AutoMintEngine {
|
|
|
162
166
|
throw new errors_1.TTTTimeSynthesisError(`[PoT] Insufficient confidence`, `Calculated confidence ${pot.confidence} is below required 0.5`, `Ensure more NTP sources are reachable or decrease uncertainty.`);
|
|
163
167
|
}
|
|
164
168
|
const potHash = ethers_1.ethers.keccak256(ethers_1.ethers.toUtf8Bytes(JSON.stringify(pot, (key, value) => typeof value === 'bigint' ? value.toString() : value)));
|
|
169
|
+
// 1-2. Ed25519 issuer signature for non-repudiation
|
|
170
|
+
if (this.potSigner) {
|
|
171
|
+
pot.issuerSignature = this.potSigner.signPot(potHash);
|
|
172
|
+
logger_1.logger.info(`[AutoMint] PoT signed by issuer ${this.potSigner.getPubKeyHex().substring(0, 16)}...`);
|
|
173
|
+
}
|
|
165
174
|
// 2. tokenId 생성 (keccak256)
|
|
166
175
|
// chainId, poolAddress, timestamp 기반 유니크 ID
|
|
167
176
|
const tokenId = ethers_1.ethers.keccak256(ethers_1.ethers.AbiCoder.defaultAbiCoder().encode(["uint256", "address", "uint64"], [BigInt(this.config.chainId), this.config.poolAddress, synthesized.timestamp]));
|
package/dist/grg_forward.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
export declare class GrgForward {
|
|
2
2
|
static golombEncode(data: Uint8Array, m?: number): Uint8Array;
|
|
3
3
|
static redstuffEncode(data: Uint8Array, shards?: number, parity?: number): Uint8Array[];
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Derives an HMAC key from GRG payload context (chainId + poolAddress).
|
|
6
|
+
* Falls back to a static domain-separation key when no context is provided.
|
|
7
|
+
*/
|
|
8
|
+
static deriveHmacKey(chainId?: number, poolAddress?: string): Buffer;
|
|
9
|
+
static golayEncodeWrapper(data: Uint8Array, hmacKey?: Buffer): Uint8Array;
|
|
10
|
+
static encode(data: Uint8Array, chainId?: number, poolAddress?: string): Uint8Array[];
|
|
6
11
|
}
|
package/dist/grg_forward.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.GrgForward = void 0;
|
|
4
4
|
const crypto_1 = require("crypto");
|
|
5
|
+
const ethers_1 = require("ethers");
|
|
5
6
|
const golay_1 = require("./golay");
|
|
6
7
|
const reed_solomon_1 = require("./reed_solomon");
|
|
7
8
|
class GrgForward {
|
|
@@ -28,18 +29,31 @@ class GrgForward {
|
|
|
28
29
|
static redstuffEncode(data, shards = 4, parity = 2) {
|
|
29
30
|
return reed_solomon_1.ReedSolomon.encode(data, shards, parity);
|
|
30
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Derives an HMAC key from GRG payload context (chainId + poolAddress).
|
|
34
|
+
* Falls back to a static domain-separation key when no context is provided.
|
|
35
|
+
*/
|
|
36
|
+
static deriveHmacKey(chainId, poolAddress) {
|
|
37
|
+
if (chainId !== undefined && poolAddress) {
|
|
38
|
+
const packed = (0, ethers_1.keccak256)(ethers_1.AbiCoder.defaultAbiCoder().encode(["uint256", "address"], [chainId, poolAddress]));
|
|
39
|
+
return Buffer.from(packed.slice(2), "hex"); // 32 bytes
|
|
40
|
+
}
|
|
41
|
+
// Default domain-separation key when no context is available
|
|
42
|
+
return Buffer.from("grg-integrity-hmac-default-key-v1");
|
|
43
|
+
}
|
|
31
44
|
// 3. Golay(24,12) Error Correction Encoding
|
|
32
|
-
static golayEncodeWrapper(data) {
|
|
45
|
+
static golayEncodeWrapper(data, hmacKey) {
|
|
33
46
|
const encoded = (0, golay_1.golayEncode)(data);
|
|
34
|
-
// 🔱 Integrity: Append 8-byte
|
|
35
|
-
const
|
|
36
|
-
const
|
|
47
|
+
// 🔱 Integrity: Append 8-byte HMAC-SHA256 of the encoded shard (keyed hash)
|
|
48
|
+
const key = hmacKey || this.deriveHmacKey();
|
|
49
|
+
const mac = (0, crypto_1.createHmac)("sha256", key).update(Buffer.from(encoded)).digest();
|
|
50
|
+
const checksum = mac.subarray(0, 8);
|
|
37
51
|
const final = new Uint8Array(encoded.length + 8);
|
|
38
52
|
final.set(encoded);
|
|
39
53
|
final.set(checksum, encoded.length);
|
|
40
54
|
return final;
|
|
41
55
|
}
|
|
42
|
-
static encode(data) {
|
|
56
|
+
static encode(data, chainId, poolAddress) {
|
|
43
57
|
// R3-P0-3: Reject empty input — roundtrip breaks ([] → [0])
|
|
44
58
|
if (data.length === 0) {
|
|
45
59
|
throw new Error("[GRG] Cannot encode empty input — roundtrip identity violation");
|
|
@@ -53,7 +67,8 @@ class GrgForward {
|
|
|
53
67
|
withLen[3] = data.length & 0xFF;
|
|
54
68
|
withLen.set(compressed, 4);
|
|
55
69
|
const shards = this.redstuffEncode(withLen);
|
|
56
|
-
|
|
70
|
+
const hmacKey = this.deriveHmacKey(chainId, poolAddress);
|
|
71
|
+
return shards.map(s => this.golayEncodeWrapper(s, hmacKey));
|
|
57
72
|
}
|
|
58
73
|
}
|
|
59
74
|
exports.GrgForward = GrgForward;
|
package/dist/grg_inverse.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare class GrgInverse {
|
|
2
|
-
static golayDecodeWrapper(data: Uint8Array): Uint8Array;
|
|
2
|
+
static golayDecodeWrapper(data: Uint8Array, hmacKey?: Buffer): Uint8Array;
|
|
3
3
|
static redstuffDecode(shards: (Uint8Array | null)[], dataShardCount?: number, parityShardCount?: number): Uint8Array;
|
|
4
4
|
private static readonly MAX_GOLOMB_Q;
|
|
5
5
|
static golombDecode(data: Uint8Array, m?: number): Uint8Array;
|
|
6
|
-
static verify(data: Uint8Array, originalShards: Uint8Array[]): boolean;
|
|
6
|
+
static verify(data: Uint8Array, originalShards: Uint8Array[], chainId?: number, poolAddress?: string): boolean;
|
|
7
7
|
}
|
package/dist/grg_inverse.js
CHANGED
|
@@ -3,21 +3,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.GrgInverse = void 0;
|
|
4
4
|
const crypto_1 = require("crypto");
|
|
5
5
|
const golay_1 = require("./golay");
|
|
6
|
+
const grg_forward_1 = require("./grg_forward");
|
|
6
7
|
const logger_1 = require("./logger");
|
|
7
8
|
const reed_solomon_1 = require("./reed_solomon");
|
|
8
9
|
class GrgInverse {
|
|
9
10
|
// 1. Golay Decoding & Integrity Check 🔱
|
|
10
|
-
static golayDecodeWrapper(data) {
|
|
11
|
+
static golayDecodeWrapper(data, hmacKey) {
|
|
11
12
|
if (data.length < 8)
|
|
12
13
|
throw new Error("GRG shard too short for checksum");
|
|
13
14
|
// Split data and checksum (last 8 bytes)
|
|
14
15
|
const encoded = data.subarray(0, data.length - 8);
|
|
15
16
|
const checksum = data.subarray(data.length - 8);
|
|
16
|
-
// Verify
|
|
17
|
-
const
|
|
18
|
-
const
|
|
17
|
+
// Verify HMAC-SHA256 Checksum (keyed hash, B1-5: 8 bytes truncated)
|
|
18
|
+
const key = hmacKey || grg_forward_1.GrgForward.deriveHmacKey();
|
|
19
|
+
const mac = (0, crypto_1.createHmac)("sha256", key).update(Buffer.from(encoded)).digest();
|
|
20
|
+
const expected = mac.subarray(0, 8);
|
|
19
21
|
if (!Buffer.from(checksum).equals(Buffer.from(expected))) {
|
|
20
|
-
throw new Error("GRG tamper detected:
|
|
22
|
+
throw new Error("GRG tamper detected: HMAC-SHA256 checksum mismatch");
|
|
21
23
|
}
|
|
22
24
|
// Proceed to Golay decode
|
|
23
25
|
const res = (0, golay_1.golayDecode)(encoded);
|
|
@@ -62,11 +64,12 @@ class GrgInverse {
|
|
|
62
64
|
}
|
|
63
65
|
return new Uint8Array(result);
|
|
64
66
|
}
|
|
65
|
-
static verify(data, originalShards) {
|
|
67
|
+
static verify(data, originalShards, chainId, poolAddress) {
|
|
66
68
|
try {
|
|
69
|
+
const hmacKey = grg_forward_1.GrgForward.deriveHmacKey(chainId, poolAddress);
|
|
67
70
|
const decodedShards = originalShards.map(s => {
|
|
68
71
|
try {
|
|
69
|
-
return this.golayDecodeWrapper(s);
|
|
72
|
+
return this.golayDecodeWrapper(s, hmacKey);
|
|
70
73
|
}
|
|
71
74
|
catch {
|
|
72
75
|
return null;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface PotSignature {
|
|
2
|
+
issuerPubKey: string;
|
|
3
|
+
signature: string;
|
|
4
|
+
issuedAt: bigint;
|
|
5
|
+
}
|
|
6
|
+
export declare class PotSigner {
|
|
7
|
+
private privateKey;
|
|
8
|
+
private publicKey;
|
|
9
|
+
private pubKeyHex;
|
|
10
|
+
constructor(privateKeyHex?: string);
|
|
11
|
+
/** Returns the hex-encoded SPKI DER public key */
|
|
12
|
+
getPubKeyHex(): string;
|
|
13
|
+
/** Returns the hex-encoded PKCS8 DER private key (for persistence) */
|
|
14
|
+
getPrivateKeyHex(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Sign a PoT hash with Ed25519.
|
|
17
|
+
* @param potHash - hex string (with or without 0x prefix) of the PoT hash
|
|
18
|
+
* @returns PotSignature with issuerPubKey, signature, and issuedAt
|
|
19
|
+
*/
|
|
20
|
+
signPot(potHash: string): PotSignature;
|
|
21
|
+
/**
|
|
22
|
+
* Verify a PotSignature against a PoT hash.
|
|
23
|
+
* @param potHash - hex string (with or without 0x prefix)
|
|
24
|
+
* @param potSig - the PotSignature to verify
|
|
25
|
+
* @param expectedPubKey - optional: reject if issuerPubKey doesn't match
|
|
26
|
+
* @returns true if signature is valid
|
|
27
|
+
*/
|
|
28
|
+
static verifyPotSignature(potHash: string, potSig: PotSignature, expectedPubKey?: string): boolean;
|
|
29
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PotSigner = void 0;
|
|
4
|
+
// sdk/src/pot_signer.ts — Ed25519 signing for Proof of Time (Non-repudiation)
|
|
5
|
+
// Uses Node.js built-in crypto.sign/verify with Ed25519
|
|
6
|
+
const crypto_1 = require("crypto");
|
|
7
|
+
class PotSigner {
|
|
8
|
+
privateKey;
|
|
9
|
+
publicKey;
|
|
10
|
+
pubKeyHex;
|
|
11
|
+
constructor(privateKeyHex) {
|
|
12
|
+
if (privateKeyHex) {
|
|
13
|
+
// Import existing key from PKCS8 DER hex
|
|
14
|
+
const keyBuffer = Buffer.from(privateKeyHex, 'hex');
|
|
15
|
+
this.privateKey = (0, crypto_1.createPrivateKey)({ key: keyBuffer, format: 'der', type: 'pkcs8' });
|
|
16
|
+
this.publicKey = (0, crypto_1.createPublicKey)(this.privateKey);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
// Generate new Ed25519 keypair
|
|
20
|
+
const { privateKey, publicKey } = (0, crypto_1.generateKeyPairSync)('ed25519');
|
|
21
|
+
this.privateKey = privateKey;
|
|
22
|
+
this.publicKey = publicKey;
|
|
23
|
+
}
|
|
24
|
+
this.pubKeyHex = this.publicKey.export({ type: 'spki', format: 'der' }).toString('hex');
|
|
25
|
+
}
|
|
26
|
+
/** Returns the hex-encoded SPKI DER public key */
|
|
27
|
+
getPubKeyHex() {
|
|
28
|
+
return this.pubKeyHex;
|
|
29
|
+
}
|
|
30
|
+
/** Returns the hex-encoded PKCS8 DER private key (for persistence) */
|
|
31
|
+
getPrivateKeyHex() {
|
|
32
|
+
return this.privateKey.export({ type: 'pkcs8', format: 'der' }).toString('hex');
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Sign a PoT hash with Ed25519.
|
|
36
|
+
* @param potHash - hex string (with or without 0x prefix) of the PoT hash
|
|
37
|
+
* @returns PotSignature with issuerPubKey, signature, and issuedAt
|
|
38
|
+
*/
|
|
39
|
+
signPot(potHash) {
|
|
40
|
+
const data = Buffer.from(potHash.startsWith('0x') ? potHash.slice(2) : potHash, 'hex');
|
|
41
|
+
const sig = (0, crypto_1.sign)(null, data, this.privateKey);
|
|
42
|
+
return {
|
|
43
|
+
issuerPubKey: this.pubKeyHex,
|
|
44
|
+
signature: sig.toString('hex'),
|
|
45
|
+
issuedAt: BigInt(Math.floor(Date.now() / 1000)),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Verify a PotSignature against a PoT hash.
|
|
50
|
+
* @param potHash - hex string (with or without 0x prefix)
|
|
51
|
+
* @param potSig - the PotSignature to verify
|
|
52
|
+
* @param expectedPubKey - optional: reject if issuerPubKey doesn't match
|
|
53
|
+
* @returns true if signature is valid
|
|
54
|
+
*/
|
|
55
|
+
static verifyPotSignature(potHash, potSig, expectedPubKey) {
|
|
56
|
+
if (expectedPubKey && potSig.issuerPubKey !== expectedPubKey)
|
|
57
|
+
return false;
|
|
58
|
+
try {
|
|
59
|
+
const data = Buffer.from(potHash.startsWith('0x') ? potHash.slice(2) : potHash, 'hex');
|
|
60
|
+
const sigBuffer = Buffer.from(potSig.signature, 'hex');
|
|
61
|
+
const pubKey = (0, crypto_1.createPublicKey)({
|
|
62
|
+
key: Buffer.from(potSig.issuerPubKey, 'hex'),
|
|
63
|
+
format: 'der',
|
|
64
|
+
type: 'spki',
|
|
65
|
+
});
|
|
66
|
+
return (0, crypto_1.verify)(null, data, pubKey, sigBuffer);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.PotSigner = PotSigner;
|
package/dist/time_synthesis.d.ts
CHANGED
|
@@ -13,6 +13,9 @@ export declare class NTPSource implements TimeSource {
|
|
|
13
13
|
}
|
|
14
14
|
export declare class TimeSynthesis {
|
|
15
15
|
private sources;
|
|
16
|
+
private usedNonces;
|
|
17
|
+
private readonly MAX_NONCE_CACHE;
|
|
18
|
+
private readonly NONCE_TTL_MS;
|
|
16
19
|
constructor(config?: {
|
|
17
20
|
sources?: string[];
|
|
18
21
|
});
|
|
@@ -24,6 +27,8 @@ export declare class TimeSynthesis {
|
|
|
24
27
|
generateProofOfTime(): Promise<ProofOfTime>;
|
|
25
28
|
/**
|
|
26
29
|
* Verify Proof of Time integrity.
|
|
30
|
+
* Fix 2: Checks expiration and nonce replay.
|
|
31
|
+
* Fix 3: Uses sourceReadings (renamed from signatures).
|
|
27
32
|
*/
|
|
28
33
|
verifyProofOfTime(pot: ProofOfTime): boolean;
|
|
29
34
|
/**
|
|
@@ -40,6 +45,7 @@ export declare class TimeSynthesis {
|
|
|
40
45
|
static deserializeFromJSON(json: string): ProofOfTime;
|
|
41
46
|
/**
|
|
42
47
|
* Serializes PoT to compact binary format.
|
|
48
|
+
* Layout: header(19) + nonce(1+N) + expiresAt(8) + readings(variable)
|
|
43
49
|
*/
|
|
44
50
|
static serializeToBinary(pot: ProofOfTime): Buffer;
|
|
45
51
|
/**
|
package/dist/time_synthesis.js
CHANGED
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.TimeSynthesis = exports.NTPSource = void 0;
|
|
37
|
+
const crypto = __importStar(require("crypto"));
|
|
37
38
|
const dgram = __importStar(require("dgram"));
|
|
38
39
|
const buffer_1 = require("buffer");
|
|
39
40
|
const ethers_1 = require("ethers");
|
|
@@ -132,6 +133,10 @@ class NTPSource {
|
|
|
132
133
|
exports.NTPSource = NTPSource;
|
|
133
134
|
class TimeSynthesis {
|
|
134
135
|
sources = [];
|
|
136
|
+
// Fix 2: Bounded nonce replay cache (max 10K entries, 60s TTL) — same pattern as protocol_fee.ts
|
|
137
|
+
usedNonces = new Map();
|
|
138
|
+
MAX_NONCE_CACHE = 10000;
|
|
139
|
+
NONCE_TTL_MS = 60000; // 60 seconds
|
|
135
140
|
constructor(config) {
|
|
136
141
|
const sourceNames = config?.sources || ['nist', 'kriss', 'google'];
|
|
137
142
|
for (const s of sourceNames) {
|
|
@@ -233,18 +238,23 @@ class TimeSynthesis {
|
|
|
233
238
|
finalUncertainty = readings[mid].uncertainty;
|
|
234
239
|
finalStratum = readings[mid].stratum;
|
|
235
240
|
}
|
|
236
|
-
const
|
|
241
|
+
const sourceReadings = readings.map(r => ({
|
|
237
242
|
source: r.source,
|
|
238
243
|
timestamp: r.timestamp,
|
|
239
244
|
uncertainty: r.uncertainty
|
|
240
245
|
}));
|
|
246
|
+
// Fix 2: PoT nonce + expiration for replay protection
|
|
247
|
+
const nonce = crypto.randomBytes(16).toString("hex");
|
|
248
|
+
const expiresAt = BigInt(Date.now()) + 60000n; // +60 seconds
|
|
241
249
|
const pot = {
|
|
242
250
|
timestamp: finalTimestamp,
|
|
243
251
|
uncertainty: finalUncertainty,
|
|
244
252
|
sources: readings.length,
|
|
245
253
|
stratum: finalStratum,
|
|
246
254
|
confidence: readings.length / this.sources.length,
|
|
247
|
-
|
|
255
|
+
sourceReadings,
|
|
256
|
+
nonce,
|
|
257
|
+
expiresAt,
|
|
248
258
|
};
|
|
249
259
|
// Verification Logic: Ensure all source timestamps are within tolerance of synthesized median
|
|
250
260
|
if (!this.verifyProofOfTime(pot)) {
|
|
@@ -254,17 +264,44 @@ class TimeSynthesis {
|
|
|
254
264
|
}
|
|
255
265
|
/**
|
|
256
266
|
* Verify Proof of Time integrity.
|
|
267
|
+
* Fix 2: Checks expiration and nonce replay.
|
|
268
|
+
* Fix 3: Uses sourceReadings (renamed from signatures).
|
|
257
269
|
*/
|
|
258
270
|
verifyProofOfTime(pot) {
|
|
259
271
|
const TOLERANCE_NS = 100000000n; // 100ms
|
|
260
|
-
if (pot.
|
|
272
|
+
if (pot.sourceReadings.length === 0)
|
|
261
273
|
return false;
|
|
262
274
|
if (pot.confidence <= 0)
|
|
263
275
|
return false;
|
|
264
|
-
|
|
276
|
+
// Fix 2: Expiration check
|
|
277
|
+
if (BigInt(Date.now()) > pot.expiresAt) {
|
|
278
|
+
logger_1.logger.warn(`[TimeSynthesis] PoT expired at ${pot.expiresAt}`);
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
// Fix 2: Nonce replay protection with bounded cache + TTL cleanup
|
|
282
|
+
const now = Date.now();
|
|
283
|
+
// TTL cleanup pass (evict expired entries)
|
|
284
|
+
if (this.usedNonces.size > this.MAX_NONCE_CACHE / 2) {
|
|
285
|
+
for (const [k, ts] of this.usedNonces) {
|
|
286
|
+
if (now - ts > this.NONCE_TTL_MS)
|
|
287
|
+
this.usedNonces.delete(k);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (this.usedNonces.has(pot.nonce)) {
|
|
291
|
+
logger_1.logger.warn(`[TimeSynthesis] Duplicate nonce detected: ${pot.nonce}`);
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
if (this.usedNonces.size >= this.MAX_NONCE_CACHE) {
|
|
295
|
+
// Evict oldest entry
|
|
296
|
+
const oldest = this.usedNonces.keys().next().value;
|
|
297
|
+
if (oldest !== undefined)
|
|
298
|
+
this.usedNonces.delete(oldest);
|
|
299
|
+
}
|
|
300
|
+
this.usedNonces.set(pot.nonce, now);
|
|
301
|
+
for (const sig of pot.sourceReadings) {
|
|
265
302
|
const diff = sig.timestamp > pot.timestamp ? sig.timestamp - pot.timestamp : pot.timestamp - sig.timestamp;
|
|
266
303
|
if (diff > TOLERANCE_NS) {
|
|
267
|
-
logger_1.logger.warn(`[TimeSynthesis]
|
|
304
|
+
logger_1.logger.warn(`[TimeSynthesis] Reading from ${sig.source} outside tolerance: ${diff}ns`);
|
|
268
305
|
return false;
|
|
269
306
|
}
|
|
270
307
|
}
|
|
@@ -296,7 +333,8 @@ class TimeSynthesis {
|
|
|
296
333
|
return {
|
|
297
334
|
...data,
|
|
298
335
|
timestamp: BigInt(data.timestamp),
|
|
299
|
-
|
|
336
|
+
expiresAt: BigInt(data.expiresAt),
|
|
337
|
+
sourceReadings: data.sourceReadings.map((s) => ({
|
|
300
338
|
...s,
|
|
301
339
|
timestamp: BigInt(s.timestamp)
|
|
302
340
|
}))
|
|
@@ -304,11 +342,14 @@ class TimeSynthesis {
|
|
|
304
342
|
}
|
|
305
343
|
/**
|
|
306
344
|
* Serializes PoT to compact binary format.
|
|
345
|
+
* Layout: header(19) + nonce(1+N) + expiresAt(8) + readings(variable)
|
|
307
346
|
*/
|
|
308
347
|
static serializeToBinary(pot) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
348
|
+
const nonceBytes = buffer_1.Buffer.from(pot.nonce, "utf8");
|
|
349
|
+
// Header: timestamp(8) + uncertainty(4) + sources(1) + stratum(1) + confidence(4) + readingCount(1) = 19
|
|
350
|
+
// + nonceLen(1) + nonce(N) + expiresAt(8)
|
|
351
|
+
let size = 19 + 1 + nonceBytes.length + 8;
|
|
352
|
+
for (const sig of pot.sourceReadings) {
|
|
312
353
|
size += 1 + sig.source.length + 8 + 4; // nameLen(1) + name(N) + ts(8) + unc(4)
|
|
313
354
|
}
|
|
314
355
|
const buf = buffer_1.Buffer.alloc(size);
|
|
@@ -323,9 +364,17 @@ class TimeSynthesis {
|
|
|
323
364
|
offset += 1;
|
|
324
365
|
buf.writeFloatBE(pot.confidence, offset);
|
|
325
366
|
offset += 4;
|
|
326
|
-
buf.writeUInt8(pot.
|
|
367
|
+
buf.writeUInt8(pot.sourceReadings.length, offset);
|
|
368
|
+
offset += 1;
|
|
369
|
+
// Nonce
|
|
370
|
+
buf.writeUInt8(nonceBytes.length, offset);
|
|
327
371
|
offset += 1;
|
|
328
|
-
|
|
372
|
+
nonceBytes.copy(buf, offset);
|
|
373
|
+
offset += nonceBytes.length;
|
|
374
|
+
// ExpiresAt
|
|
375
|
+
buf.writeBigUInt64BE(pot.expiresAt, offset);
|
|
376
|
+
offset += 8;
|
|
377
|
+
for (const sig of pot.sourceReadings) {
|
|
329
378
|
buf.writeUInt8(sig.source.length, offset);
|
|
330
379
|
offset += 1;
|
|
331
380
|
buf.write(sig.source, offset);
|
|
@@ -354,7 +403,15 @@ class TimeSynthesis {
|
|
|
354
403
|
offset += 4;
|
|
355
404
|
const sigCount = buf.readUInt8(offset);
|
|
356
405
|
offset += 1;
|
|
357
|
-
|
|
406
|
+
// Nonce
|
|
407
|
+
const nonceLen = buf.readUInt8(offset);
|
|
408
|
+
offset += 1;
|
|
409
|
+
const nonce = buf.toString('utf8', offset, offset + nonceLen);
|
|
410
|
+
offset += nonceLen;
|
|
411
|
+
// ExpiresAt
|
|
412
|
+
const expiresAt = buf.readBigUInt64BE(offset);
|
|
413
|
+
offset += 8;
|
|
414
|
+
const sourceReadings = [];
|
|
358
415
|
for (let i = 0; i < sigCount; i++) {
|
|
359
416
|
const nameLen = buf.readUInt8(offset);
|
|
360
417
|
offset += 1;
|
|
@@ -364,9 +421,9 @@ class TimeSynthesis {
|
|
|
364
421
|
offset += 8;
|
|
365
422
|
const unc = buf.readFloatBE(offset);
|
|
366
423
|
offset += 4;
|
|
367
|
-
|
|
424
|
+
sourceReadings.push({ source, timestamp: ts, uncertainty: unc });
|
|
368
425
|
}
|
|
369
|
-
return { timestamp, uncertainty, sources, stratum, confidence,
|
|
426
|
+
return { timestamp, uncertainty, sources, stratum, confidence, sourceReadings, nonce, expiresAt };
|
|
370
427
|
}
|
|
371
428
|
}
|
|
372
429
|
exports.TimeSynthesis = TimeSynthesis;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Signer } from "ethers";
|
|
2
2
|
import { SignerConfig } from "./signer";
|
|
3
3
|
import { NetworkConfig } from "./networks";
|
|
4
|
+
import { PotSignature } from "./pot_signer";
|
|
4
5
|
export type TierType = "T0_epoch" | "T1_block" | "T2_slot" | "T3_micro";
|
|
5
6
|
export declare const TierIntervals: Record<TierType, number>;
|
|
6
7
|
/**
|
|
@@ -133,9 +134,13 @@ export interface ProofOfTime {
|
|
|
133
134
|
sources: number;
|
|
134
135
|
stratum: number;
|
|
135
136
|
confidence: number;
|
|
136
|
-
|
|
137
|
+
sourceReadings: {
|
|
137
138
|
source: string;
|
|
138
139
|
timestamp: bigint;
|
|
139
140
|
uncertainty: number;
|
|
140
141
|
}[];
|
|
142
|
+
nonce: string;
|
|
143
|
+
expiresAt: bigint;
|
|
144
|
+
issuerSignature?: PotSignature;
|
|
141
145
|
}
|
|
146
|
+
export type { PotSignature } from "./pot_signer";
|