yakmesh 2.8.2 → 3.0.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/CHANGELOG.md +637 -0
- package/CONTRIBUTING.md +42 -0
- package/Caddyfile +77 -0
- package/README.md +119 -29
- package/adapters/adapter-mlv-bible/README.md +124 -0
- package/adapters/adapter-mlv-bible/index.js +400 -0
- package/adapters/chat-mod-adapter.js +532 -0
- package/adapters/content-adapter.js +273 -0
- package/content/api.js +50 -41
- package/content/index.js +2 -2
- package/content/store.js +355 -173
- package/dashboard/index.html +19 -3
- package/database/replication.js +117 -37
- package/docs/CRYPTO-AGILITY.md +204 -0
- package/docs/MTLS-RESEARCH.md +367 -0
- package/docs/NAMCHE-SPEC.md +681 -0
- package/docs/PEERQUANTA-YAKMESH-INTEGRATION.md +407 -0
- package/docs/PRECISION-DISCLOSURE.md +96 -0
- package/docs/README.md +76 -0
- package/docs/ROADMAP-2.4.0.md +447 -0
- package/docs/ROADMAP-2.5.0.md +244 -0
- package/docs/SECURITY-AUDIT-REPORT.md +306 -0
- package/docs/SST-INTEGRATION.md +712 -0
- package/docs/STEADYWATCH-IMPLEMENTATION.md +303 -0
- package/docs/TERNARY-AUDIT-REPORT.md +247 -0
- package/docs/TME-FAQ.md +221 -0
- package/docs/WHITEPAPER.md +623 -0
- package/docs/adapters.html +1001 -0
- package/docs/advanced-systems.html +1045 -0
- package/docs/annex.html +1046 -0
- package/docs/api.html +970 -0
- package/docs/business/response-templates.md +160 -0
- package/docs/c2c.html +1225 -0
- package/docs/cli.html +1332 -0
- package/docs/configuration.html +1248 -0
- package/docs/darshan.html +1085 -0
- package/docs/dharma.html +966 -0
- package/docs/docs-bundle.html +1075 -0
- package/docs/docs.css +3120 -0
- package/docs/docs.js +556 -0
- package/docs/doko.html +969 -0
- package/docs/geo-proof.html +858 -0
- package/docs/getting-started.html +840 -0
- package/docs/gumba-tutorial.html +1144 -0
- package/docs/gumba.html +1098 -0
- package/docs/index.html +914 -0
- package/docs/jhilke.html +1312 -0
- package/docs/karma.html +1100 -0
- package/docs/katha.html +1037 -0
- package/docs/lama.html +978 -0
- package/docs/mandala.html +1067 -0
- package/docs/mani.html +964 -0
- package/docs/mantra.html +967 -0
- package/docs/mesh.html +1409 -0
- package/docs/nakpak.html +869 -0
- package/docs/namche.html +928 -0
- package/docs/nav-order.json +53 -0
- package/docs/prahari.html +1043 -0
- package/docs/prism-bash.min.js +1 -0
- package/docs/prism-javascript.min.js +1 -0
- package/docs/prism-json.min.js +1 -0
- package/docs/prism-tomorrow.min.css +1 -0
- package/docs/prism.min.js +1 -0
- package/docs/privacy.html +699 -0
- package/docs/quick-reference.html +1181 -0
- package/docs/sakshi.html +1402 -0
- package/docs/sandboxing.md +386 -0
- package/docs/seva.html +911 -0
- package/docs/sherpa.html +871 -0
- package/docs/studio.html +860 -0
- package/docs/stupa.html +995 -0
- package/docs/tailwind.min.css +2 -0
- package/docs/tattva.html +1332 -0
- package/docs/terms.html +686 -0
- package/docs/time-server-deployment.md +166 -0
- package/docs/time-sources.html +1392 -0
- package/docs/tivra.html +1127 -0
- package/docs/trademark-policy.html +686 -0
- package/docs/tribhuj.html +1183 -0
- package/docs/trust-security.html +1029 -0
- package/docs/tutorials/backup-recovery.html +654 -0
- package/docs/tutorials/dashboard.html +604 -0
- package/docs/tutorials/domain-setup.html +605 -0
- package/docs/tutorials/host-website.html +456 -0
- package/docs/tutorials/mesh-network.html +505 -0
- package/docs/tutorials/mobile-access.html +445 -0
- package/docs/tutorials/privacy.html +467 -0
- package/docs/tutorials/raspberry-pi.html +600 -0
- package/docs/tutorials/security-basics.html +539 -0
- package/docs/tutorials/share-files.html +431 -0
- package/docs/tutorials/troubleshooting.html +637 -0
- package/docs/tutorials/trust-karma.html +419 -0
- package/docs/tutorials/yak-protocol.html +456 -0
- package/docs/tutorials.html +1034 -0
- package/docs/vani.html +1270 -0
- package/docs/webserver.html +809 -0
- package/docs/yak-protocol.html +940 -0
- package/docs/yak-timeserver-design.md +475 -0
- package/docs/yakapp.html +1015 -0
- package/docs/ypc27.html +1069 -0
- package/docs/yurt.html +1344 -0
- package/embedded-docs/bundle.js +334 -74
- package/gossip/protocol.js +247 -27
- package/identity/key-resolver.js +262 -0
- package/identity/machine-seed.js +632 -0
- package/identity/node-key.js +669 -368
- package/identity/tribhuj-ratchet.js +506 -0
- package/knowledge-base.js +37 -8
- package/launcher/yakmesh.bat +62 -0
- package/launcher/yakmesh.sh +70 -0
- package/mesh/annex.js +462 -108
- package/mesh/beacon-broadcast.js +113 -1
- package/mesh/darshan.js +1718 -0
- package/mesh/gumba.js +1567 -0
- package/mesh/jhilke.js +651 -0
- package/mesh/katha.js +1012 -0
- package/mesh/nakpak-routing.js +8 -5
- package/mesh/network.js +724 -34
- package/mesh/pulse-sync.js +4 -1
- package/mesh/rate-limiter.js +127 -15
- package/mesh/seva.js +526 -0
- package/mesh/sherpa-discovery.js +89 -8
- package/mesh/sybil-defense.js +19 -5
- package/mesh/temporal-encoder.js +4 -3
- package/mesh/vani.js +1364 -0
- package/mesh/yurt.js +1340 -0
- package/models/entropy-sentinel.onnx +0 -0
- package/models/karma-trust.onnx +0 -0
- package/models/manifest.json +43 -0
- package/models/sakshi-anomaly.onnx +0 -0
- package/oracle/code-proof-protocol.js +7 -6
- package/oracle/codebase-lock.js +257 -28
- package/oracle/index.js +74 -15
- package/oracle/ma902-snmp.js +678 -0
- package/oracle/module-sealer.js +5 -3
- package/oracle/network-identity.js +16 -0
- package/oracle/packet-checksum.js +201 -0
- package/oracle/sst.js +579 -0
- package/oracle/ternary-144t.js +714 -0
- package/oracle/ternary-ml.js +481 -0
- package/oracle/time-api.js +239 -0
- package/oracle/time-source.js +137 -47
- package/oracle/validation-oracle-hardened.js +1111 -1071
- package/oracle/validation-oracle.js +4 -2
- package/oracle/ypc27.js +211 -0
- package/package.json +20 -3
- package/protocol/yak-handler.js +35 -9
- package/protocol/yak-protocol.js +28 -13
- package/reference/cpp/yakmesh_mceliece_shard.cpp +168 -0
- package/reference/cpp/yakmesh_ypc27.cpp +179 -0
- package/sbom.json +87 -0
- package/scripts/security-audit.mjs +264 -0
- package/scripts/update-docs-nav.js +194 -0
- package/scripts/update-docs-sidebar.cjs +164 -0
- package/security/crypto-config.js +4 -3
- package/security/dharma-moderation.js +517 -0
- package/security/doko-identity.js +193 -143
- package/security/domain-consensus.js +86 -85
- package/security/fs-hardening.js +620 -0
- package/security/hardware-attestation.js +5 -3
- package/security/hybrid-trust.js +227 -87
- package/security/karma-rate-limiter.js +692 -0
- package/security/khata-protocol.js +22 -21
- package/security/khata-trust-integration.js +277 -150
- package/security/memory-safety.js +635 -0
- package/security/mesh-auth.js +11 -10
- package/security/mesh-revocation.js +373 -5
- package/security/namche-gateway.js +298 -69
- package/security/sakshi.js +460 -3
- package/security/sangha.js +770 -0
- package/security/secure-config.js +473 -0
- package/security/silicon-parity.js +13 -10
- package/security/steadywatch.js +1142 -0
- package/security/strike-system.js +32 -3
- package/security/temporal-signing.js +488 -0
- package/security/trit-commitment.js +464 -0
- package/server/crypto/annex.js +247 -0
- package/server/darshan-api.js +343 -0
- package/server/index.js +3259 -362
- package/server/komm-api.js +668 -0
- package/utils/accel.js +2273 -0
- package/utils/ternary-id.js +79 -0
- package/utils/verify-worker.js +57 -0
- package/webserver/index.js +95 -5
- package/assets/yakmesh-logo.png +0 -0
- package/assets/yakmesh-logo.svg +0 -80
- package/assets/yakmesh-logo2.png +0 -0
- package/assets/yakmesh-logo2sm.png +0 -0
- package/assets/ymsm.png +0 -0
- package/website/assets/silhouettes/adapters.svg +0 -107
- package/website/assets/silhouettes/api-endpoints.svg +0 -115
- package/website/assets/silhouettes/atomic-clock.svg +0 -83
- package/website/assets/silhouettes/base-camp.svg +0 -81
- package/website/assets/silhouettes/bridge.svg +0 -69
- package/website/assets/silhouettes/docs-bundle.svg +0 -113
- package/website/assets/silhouettes/doko-basket.svg +0 -70
- package/website/assets/silhouettes/fortress.svg +0 -93
- package/website/assets/silhouettes/gateway.svg +0 -54
- package/website/assets/silhouettes/gears.svg +0 -93
- package/website/assets/silhouettes/globe-satellite.svg +0 -67
- package/website/assets/silhouettes/karma-wheel.svg +0 -137
- package/website/assets/silhouettes/lama-council.svg +0 -141
- package/website/assets/silhouettes/mandala-network.svg +0 -169
- package/website/assets/silhouettes/mani-stones.svg +0 -149
- package/website/assets/silhouettes/mantra-wheel.svg +0 -116
- package/website/assets/silhouettes/mesh-nodes.svg +0 -113
- package/website/assets/silhouettes/nakpak.svg +0 -56
- package/website/assets/silhouettes/peak-lightning.svg +0 -73
- package/website/assets/silhouettes/sherpa.svg +0 -69
- package/website/assets/silhouettes/stupa-tower.svg +0 -119
- package/website/assets/silhouettes/tattva-eye.svg +0 -78
- package/website/assets/silhouettes/terminal.svg +0 -74
- package/website/assets/silhouettes/webserver.svg +0 -145
- package/website/assets/silhouettes/yak.svg +0 -78
- package/website/assets/yakmesh-logo.png +0 -0
- package/website/assets/yakmesh-logo.webp +0 -0
- package/website/assets/yakmesh-logo128x140.webp +0 -0
- package/website/assets/yakmesh-logo2.png +0 -0
- package/website/assets/yakmesh-logo2.svg +0 -51
- package/website/assets/yakmesh-logo40x44.webp +0 -0
- package/website/assets/yakmesh.gif +0 -0
- package/website/assets/yakmesh.ico +0 -0
- package/website/assets/yakmesh.jpg +0 -0
- package/website/assets/yakmesh.pdf +0 -0
- package/website/assets/yakmesh.png +0 -0
- package/website/assets/yakmesh.svg +0 -70
- package/website/assets/yakmesh128.webp +0 -0
- package/website/assets/yakmesh32.png +0 -0
- package/website/assets/yakmesh32.svg +0 -65
- package/website/assets/yakmesh32o.ico +0 -2
- package/website/assets/yakmesh32o.svg +0 -65
- package/website/assets/yakmesh32o.svgz +0 -0
|
@@ -14,9 +14,17 @@
|
|
|
14
14
|
* Mathematical principle: A node can escape one identity,
|
|
15
15
|
* but not its hardware. The silicon remembers.
|
|
16
16
|
*
|
|
17
|
+
* Strike verification uses TRIBHUJ balanced ternary:
|
|
18
|
+
* POSITIVE (+1): Confirmed by network consensus
|
|
19
|
+
* NEUTRAL ( 0): Pending — awaiting verification
|
|
20
|
+
* NEGATIVE (-1): Disputed — contested by the accused node
|
|
21
|
+
*
|
|
17
22
|
* @module security/strike-system
|
|
18
23
|
*/
|
|
19
24
|
|
|
25
|
+
// ═══ TRIBHUJ — Balanced ternary for strike verification state ═══
|
|
26
|
+
import { POSITIVE, NEUTRAL, NEGATIVE } from '../oracle/tribhuj.js';
|
|
27
|
+
|
|
20
28
|
// Constants
|
|
21
29
|
const STRIKE_LEVELS = {
|
|
22
30
|
CLEAN: 0, // No strikes
|
|
@@ -79,19 +87,40 @@ class StrikeEvent {
|
|
|
79
87
|
this.timestamp = options.timestamp || Date.now();
|
|
80
88
|
this.attestors = options.attestors || [];
|
|
81
89
|
this.evidence = options.evidence || {};
|
|
82
|
-
this.verified =
|
|
90
|
+
this.verified = NEUTRAL; // TRIBHUJ trit: starts NEUTRAL (pending)
|
|
83
91
|
}
|
|
84
92
|
|
|
85
93
|
/**
|
|
86
|
-
* Mark this strike as verified by network consensus
|
|
94
|
+
* Mark this strike as verified by network consensus → POSITIVE
|
|
87
95
|
* @param {string[]} verifyingNodes - Nodes that verified the strike
|
|
88
96
|
*/
|
|
89
97
|
verify(verifyingNodes) {
|
|
90
|
-
this.verified =
|
|
98
|
+
this.verified = POSITIVE;
|
|
91
99
|
this.verifiedBy = verifyingNodes;
|
|
92
100
|
this.verifiedAt = Date.now();
|
|
93
101
|
}
|
|
94
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Mark this strike as disputed by the accused node → NEGATIVE
|
|
105
|
+
* @param {string} disputedBy - Node disputing the strike
|
|
106
|
+
* @param {string} disputeReason - Why the strike is disputed
|
|
107
|
+
*/
|
|
108
|
+
dispute(disputedBy, disputeReason) {
|
|
109
|
+
this.verified = NEGATIVE;
|
|
110
|
+
this.disputedBy = disputedBy;
|
|
111
|
+
this.disputeReason = disputeReason;
|
|
112
|
+
this.disputedAt = Date.now();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Check if this strike is confirmed (POSITIVE trit) */
|
|
116
|
+
get isConfirmed() { return this.verified === POSITIVE; }
|
|
117
|
+
|
|
118
|
+
/** Check if this strike is pending (NEUTRAL trit) */
|
|
119
|
+
get isPending() { return this.verified === NEUTRAL; }
|
|
120
|
+
|
|
121
|
+
/** Check if this strike is disputed (NEGATIVE trit) */
|
|
122
|
+
get isDisputed() { return this.verified === NEGATIVE; }
|
|
123
|
+
|
|
95
124
|
toJSON() {
|
|
96
125
|
return {
|
|
97
126
|
id: this.id,
|
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Yakmesh Temporal Code Signing
|
|
3
|
+
*
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
* PHILOSOPHY: SIGNATURES THAT BREATHE
|
|
6
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
+
*
|
|
8
|
+
* Traditional code signing: Sign once, valid forever (until key compromise).
|
|
9
|
+
* Temporal signatures: Bound to GPS time, auto-expire, require re-attestation.
|
|
10
|
+
*
|
|
11
|
+
* This creates a "living signature" that:
|
|
12
|
+
* - Proves code was signed at a specific GPS time (±10ms precision)
|
|
13
|
+
* - Automatically expires after a configurable period (default: 30 days)
|
|
14
|
+
* - Forces regular re-attestation of releases
|
|
15
|
+
* - Makes stolen/leaked signatures useless after expiry
|
|
16
|
+
*
|
|
17
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
18
|
+
* SIGNATURE STRUCTURE
|
|
19
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
20
|
+
*
|
|
21
|
+
* {
|
|
22
|
+
* version: 1,
|
|
23
|
+
* codeHash: SHA3-256 of the code/bundle,
|
|
24
|
+
* signerPubKey: ML-DSA-65 public key,
|
|
25
|
+
* signedAt: GPS timestamp (ms since epoch),
|
|
26
|
+
* expiresAt: signedAt + validity period,
|
|
27
|
+
* networkId: Network ID for cross-network prevention,
|
|
28
|
+
* signature: ML-DSA-65 signature over all above fields
|
|
29
|
+
* }
|
|
30
|
+
*
|
|
31
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
32
|
+
* VERIFICATION
|
|
33
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
34
|
+
*
|
|
35
|
+
* 1. Check signature validity (ML-DSA-65 verify)
|
|
36
|
+
* 2. Check current GPS time < expiresAt
|
|
37
|
+
* 3. Check signedAt is in the past (prevent future-dated signatures)
|
|
38
|
+
* 4. Check networkId matches current network
|
|
39
|
+
* 5. Check signerPubKey is in trusted signers list
|
|
40
|
+
*
|
|
41
|
+
* @module security/temporal-signing
|
|
42
|
+
* @version 1.0.0
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
import { createLogger } from '../utils/logger.js';
|
|
46
|
+
import { sha3_256, mlDsa65Sign, mlDsa65Verify } from '../utils/accel.js';
|
|
47
|
+
import { bytesToHex, hexToBytes } from '@noble/hashes/utils.js';
|
|
48
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
49
|
+
import { join } from 'path';
|
|
50
|
+
import { EventEmitter } from 'events';
|
|
51
|
+
|
|
52
|
+
const log = createLogger('security:temporal-signing');
|
|
53
|
+
|
|
54
|
+
// =============================================================================
|
|
55
|
+
// CONSTANTS
|
|
56
|
+
// =============================================================================
|
|
57
|
+
|
|
58
|
+
/** Signature version */
|
|
59
|
+
const SIGNATURE_VERSION = 1;
|
|
60
|
+
|
|
61
|
+
/** Default validity period (30 days in ms) */
|
|
62
|
+
const DEFAULT_VALIDITY_MS = 30 * 24 * 60 * 60 * 1000;
|
|
63
|
+
|
|
64
|
+
/** Minimum validity period (1 hour) */
|
|
65
|
+
const MIN_VALIDITY_MS = 60 * 60 * 1000;
|
|
66
|
+
|
|
67
|
+
/** Maximum clock skew tolerance (10 seconds) */
|
|
68
|
+
const MAX_CLOCK_SKEW_MS = 10000;
|
|
69
|
+
|
|
70
|
+
/** Grace period after expiry (allows time for re-signing) */
|
|
71
|
+
const EXPIRY_GRACE_MS = 24 * 60 * 60 * 1000; // 1 day
|
|
72
|
+
|
|
73
|
+
// =============================================================================
|
|
74
|
+
// TEMPORAL SIGNATURE
|
|
75
|
+
// =============================================================================
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* TemporalSignature — A time-bound code signature
|
|
79
|
+
*/
|
|
80
|
+
export class TemporalSignature {
|
|
81
|
+
version;
|
|
82
|
+
codeHash;
|
|
83
|
+
signerPubKey;
|
|
84
|
+
signedAt;
|
|
85
|
+
expiresAt;
|
|
86
|
+
networkId;
|
|
87
|
+
signature;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {object} data - Signature data
|
|
91
|
+
*/
|
|
92
|
+
constructor(data) {
|
|
93
|
+
this.version = data.version || SIGNATURE_VERSION;
|
|
94
|
+
this.codeHash = data.codeHash;
|
|
95
|
+
this.signerPubKey = data.signerPubKey;
|
|
96
|
+
this.signedAt = data.signedAt;
|
|
97
|
+
this.expiresAt = data.expiresAt;
|
|
98
|
+
this.networkId = data.networkId;
|
|
99
|
+
this.signature = data.signature || null;
|
|
100
|
+
|
|
101
|
+
Object.seal(this);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get the signable payload (all fields except signature)
|
|
106
|
+
* @returns {Uint8Array}
|
|
107
|
+
*/
|
|
108
|
+
getSignablePayload() {
|
|
109
|
+
const payload = JSON.stringify({
|
|
110
|
+
version: this.version,
|
|
111
|
+
codeHash: this.codeHash,
|
|
112
|
+
signerPubKey: this.signerPubKey,
|
|
113
|
+
signedAt: this.signedAt,
|
|
114
|
+
expiresAt: this.expiresAt,
|
|
115
|
+
networkId: this.networkId,
|
|
116
|
+
});
|
|
117
|
+
return new TextEncoder().encode(payload);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Check if signature has expired
|
|
122
|
+
* @param {number} currentTime - Current GPS time in ms
|
|
123
|
+
* @param {boolean} useGrace - Whether to use grace period
|
|
124
|
+
* @returns {boolean}
|
|
125
|
+
*/
|
|
126
|
+
isExpired(currentTime, useGrace = false) {
|
|
127
|
+
const effectiveExpiry = useGrace
|
|
128
|
+
? this.expiresAt + EXPIRY_GRACE_MS
|
|
129
|
+
: this.expiresAt;
|
|
130
|
+
return currentTime > effectiveExpiry;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get remaining validity time
|
|
135
|
+
* @param {number} currentTime - Current GPS time in ms
|
|
136
|
+
* @returns {number} Remaining ms (negative if expired)
|
|
137
|
+
*/
|
|
138
|
+
getRemainingValidity(currentTime) {
|
|
139
|
+
return this.expiresAt - currentTime;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get human-readable expiry info
|
|
144
|
+
* @param {number} currentTime - Current GPS time in ms
|
|
145
|
+
* @returns {string}
|
|
146
|
+
*/
|
|
147
|
+
getExpiryInfo(currentTime) {
|
|
148
|
+
const remaining = this.getRemainingValidity(currentTime);
|
|
149
|
+
if (remaining < 0) {
|
|
150
|
+
const expired = Math.abs(remaining);
|
|
151
|
+
const days = Math.floor(expired / (24 * 60 * 60 * 1000));
|
|
152
|
+
return `Expired ${days} days ago`;
|
|
153
|
+
}
|
|
154
|
+
const days = Math.floor(remaining / (24 * 60 * 60 * 1000));
|
|
155
|
+
const hours = Math.floor((remaining % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000));
|
|
156
|
+
return `Valid for ${days}d ${hours}h`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Serialize to JSON
|
|
161
|
+
* @returns {object}
|
|
162
|
+
*/
|
|
163
|
+
toJSON() {
|
|
164
|
+
return {
|
|
165
|
+
version: this.version,
|
|
166
|
+
codeHash: this.codeHash,
|
|
167
|
+
signerPubKey: this.signerPubKey,
|
|
168
|
+
signedAt: this.signedAt,
|
|
169
|
+
expiresAt: this.expiresAt,
|
|
170
|
+
networkId: this.networkId,
|
|
171
|
+
signature: this.signature,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Create from JSON
|
|
177
|
+
* @param {object|string} json
|
|
178
|
+
* @returns {TemporalSignature}
|
|
179
|
+
*/
|
|
180
|
+
static fromJSON(json) {
|
|
181
|
+
const data = typeof json === 'string' ? JSON.parse(json) : json;
|
|
182
|
+
return new TemporalSignature(data);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// =============================================================================
|
|
187
|
+
// TEMPORAL SIGNER
|
|
188
|
+
// =============================================================================
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* TemporalSigner — Creates and verifies temporal signatures
|
|
192
|
+
*/
|
|
193
|
+
export class TemporalSigner extends EventEmitter {
|
|
194
|
+
#timeSource;
|
|
195
|
+
#networkId;
|
|
196
|
+
#trustedSigners;
|
|
197
|
+
#signatureCache;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* @param {object} options
|
|
201
|
+
* @param {object} options.timeSource - GPS time source
|
|
202
|
+
* @param {string} options.networkId - Current network ID
|
|
203
|
+
*/
|
|
204
|
+
constructor({ timeSource = null, networkId = 'unknown' } = {}) {
|
|
205
|
+
super();
|
|
206
|
+
this.#timeSource = timeSource;
|
|
207
|
+
this.#networkId = networkId;
|
|
208
|
+
this.#trustedSigners = new Set();
|
|
209
|
+
this.#signatureCache = new Map();
|
|
210
|
+
|
|
211
|
+
Object.seal(this);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Bind a GPS time source
|
|
216
|
+
* @param {object} timeSource
|
|
217
|
+
*/
|
|
218
|
+
bindTimeSource(timeSource) {
|
|
219
|
+
this.#timeSource = timeSource;
|
|
220
|
+
const sourceType = timeSource?.getStatus?.()?.primarySource ||
|
|
221
|
+
timeSource?.getSourceType?.() ||
|
|
222
|
+
'unknown';
|
|
223
|
+
log.info('Temporal signer bound to time source', { type: sourceType });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Set the network ID
|
|
228
|
+
* @param {string} networkId
|
|
229
|
+
*/
|
|
230
|
+
setNetworkId(networkId) {
|
|
231
|
+
this.#networkId = networkId;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Add a trusted signer public key
|
|
236
|
+
* @param {string} pubKeyHex
|
|
237
|
+
*/
|
|
238
|
+
addTrustedSigner(pubKeyHex) {
|
|
239
|
+
this.#trustedSigners.add(pubKeyHex);
|
|
240
|
+
log.info('Added trusted signer', { pubKey: pubKeyHex.slice(0, 32) + '...' });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Remove a trusted signer
|
|
245
|
+
* @param {string} pubKeyHex
|
|
246
|
+
*/
|
|
247
|
+
removeTrustedSigner(pubKeyHex) {
|
|
248
|
+
this.#trustedSigners.delete(pubKeyHex);
|
|
249
|
+
log.info('Removed trusted signer', { pubKey: pubKeyHex.slice(0, 32) + '...' });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Get current GPS time
|
|
254
|
+
* @returns {number}
|
|
255
|
+
*/
|
|
256
|
+
#getCurrentTime() {
|
|
257
|
+
if (this.#timeSource?.getGPSTime) {
|
|
258
|
+
return this.#timeSource.getGPSTime();
|
|
259
|
+
}
|
|
260
|
+
// Fallback to system time (less secure)
|
|
261
|
+
return Date.now();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Sign code with a temporal signature
|
|
266
|
+
* @param {Uint8Array|string} code - Code content to sign
|
|
267
|
+
* @param {string} secretKeyHex - Signer's secret key
|
|
268
|
+
* @param {string} pubKeyHex - Signer's public key
|
|
269
|
+
* @param {number} validityMs - Validity period in ms
|
|
270
|
+
* @returns {TemporalSignature}
|
|
271
|
+
*/
|
|
272
|
+
sign(code, secretKeyHex, pubKeyHex, validityMs = DEFAULT_VALIDITY_MS) {
|
|
273
|
+
// Ensure minimum validity
|
|
274
|
+
if (validityMs < MIN_VALIDITY_MS) {
|
|
275
|
+
validityMs = MIN_VALIDITY_MS;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Hash the code
|
|
279
|
+
const codeBytes = typeof code === 'string'
|
|
280
|
+
? new TextEncoder().encode(code)
|
|
281
|
+
: code;
|
|
282
|
+
const codeHash = bytesToHex(sha3_256(codeBytes));
|
|
283
|
+
|
|
284
|
+
// Get GPS time
|
|
285
|
+
const signedAt = this.#getCurrentTime();
|
|
286
|
+
const expiresAt = signedAt + validityMs;
|
|
287
|
+
|
|
288
|
+
// Create signature object
|
|
289
|
+
const sig = new TemporalSignature({
|
|
290
|
+
version: SIGNATURE_VERSION,
|
|
291
|
+
codeHash,
|
|
292
|
+
signerPubKey: pubKeyHex,
|
|
293
|
+
signedAt,
|
|
294
|
+
expiresAt,
|
|
295
|
+
networkId: this.#networkId,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Sign the payload
|
|
299
|
+
const payload = sig.getSignablePayload();
|
|
300
|
+
const secretKey = hexToBytes(secretKeyHex);
|
|
301
|
+
const signature = mlDsa65Sign(payload, secretKey);
|
|
302
|
+
sig.signature = bytesToHex(signature);
|
|
303
|
+
|
|
304
|
+
log.info('Created temporal signature', {
|
|
305
|
+
codeHash: codeHash.slice(0, 16) + '...',
|
|
306
|
+
expiresIn: sig.getExpiryInfo(signedAt),
|
|
307
|
+
networkId: this.#networkId,
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
return sig;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Verify a temporal signature
|
|
315
|
+
* @param {Uint8Array|string} code - Code content to verify
|
|
316
|
+
* @param {TemporalSignature|object} sig - Signature to verify
|
|
317
|
+
* @returns {{ valid: boolean, error?: string, warnings?: string[] }}
|
|
318
|
+
*/
|
|
319
|
+
verify(code, sig) {
|
|
320
|
+
const signature = sig instanceof TemporalSignature
|
|
321
|
+
? sig
|
|
322
|
+
: TemporalSignature.fromJSON(sig);
|
|
323
|
+
|
|
324
|
+
const warnings = [];
|
|
325
|
+
const currentTime = this.#getCurrentTime();
|
|
326
|
+
|
|
327
|
+
// 1. Check version
|
|
328
|
+
if (signature.version !== SIGNATURE_VERSION) {
|
|
329
|
+
return { valid: false, error: `Unknown signature version: ${signature.version}` };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// 2. Check network ID
|
|
333
|
+
if (signature.networkId !== this.#networkId) {
|
|
334
|
+
return {
|
|
335
|
+
valid: false,
|
|
336
|
+
error: `Network mismatch: expected ${this.#networkId}, got ${signature.networkId}`,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// 3. Check trusted signer
|
|
341
|
+
if (!this.#trustedSigners.has(signature.signerPubKey)) {
|
|
342
|
+
return {
|
|
343
|
+
valid: false,
|
|
344
|
+
error: 'Signer not in trusted signers list',
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// 4. Check signedAt is not in the future (with skew tolerance)
|
|
349
|
+
if (signature.signedAt > currentTime + MAX_CLOCK_SKEW_MS) {
|
|
350
|
+
return {
|
|
351
|
+
valid: false,
|
|
352
|
+
error: `Signature is from the future: signed at ${new Date(signature.signedAt).toISOString()}`,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// 5. Check expiry
|
|
357
|
+
if (signature.isExpired(currentTime)) {
|
|
358
|
+
// Check if within grace period
|
|
359
|
+
if (!signature.isExpired(currentTime, true)) {
|
|
360
|
+
warnings.push('Signature expired but within grace period');
|
|
361
|
+
} else {
|
|
362
|
+
return {
|
|
363
|
+
valid: false,
|
|
364
|
+
error: `Signature expired: ${signature.getExpiryInfo(currentTime)}`,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// 6. Check code hash
|
|
370
|
+
const codeBytes = typeof code === 'string'
|
|
371
|
+
? new TextEncoder().encode(code)
|
|
372
|
+
: code;
|
|
373
|
+
const computedHash = bytesToHex(sha3_256(codeBytes));
|
|
374
|
+
|
|
375
|
+
if (computedHash !== signature.codeHash) {
|
|
376
|
+
return {
|
|
377
|
+
valid: false,
|
|
378
|
+
error: 'Code hash mismatch - code has been modified',
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// 7. Verify cryptographic signature
|
|
383
|
+
try {
|
|
384
|
+
const payload = signature.getSignablePayload();
|
|
385
|
+
const sigBytes = hexToBytes(signature.signature);
|
|
386
|
+
const pubKey = hexToBytes(signature.signerPubKey);
|
|
387
|
+
|
|
388
|
+
const valid = mlDsa65Verify(sigBytes, payload, pubKey);
|
|
389
|
+
|
|
390
|
+
if (!valid) {
|
|
391
|
+
return { valid: false, error: 'Cryptographic signature verification failed' };
|
|
392
|
+
}
|
|
393
|
+
} catch (e) {
|
|
394
|
+
return { valid: false, error: `Signature verification error: ${e.message}` };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Add remaining validity warning if < 7 days
|
|
398
|
+
const remaining = signature.getRemainingValidity(currentTime);
|
|
399
|
+
if (remaining < 7 * 24 * 60 * 60 * 1000) {
|
|
400
|
+
const days = Math.floor(remaining / (24 * 60 * 60 * 1000));
|
|
401
|
+
warnings.push(`Signature expires in ${days} days - consider re-signing`);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
valid: true,
|
|
406
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
407
|
+
expiryInfo: signature.getExpiryInfo(currentTime),
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Sign a release bundle
|
|
413
|
+
* @param {string} bundlePath - Path to the release bundle
|
|
414
|
+
* @param {string} secretKeyHex - Signer's secret key
|
|
415
|
+
* @param {string} pubKeyHex - Signer's public key
|
|
416
|
+
* @param {number} validityMs - Validity period
|
|
417
|
+
* @returns {TemporalSignature}
|
|
418
|
+
*/
|
|
419
|
+
signFile(bundlePath, secretKeyHex, pubKeyHex, validityMs = DEFAULT_VALIDITY_MS) {
|
|
420
|
+
const code = readFileSync(bundlePath);
|
|
421
|
+
return this.sign(code, secretKeyHex, pubKeyHex, validityMs);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Verify a release bundle
|
|
426
|
+
* @param {string} bundlePath - Path to the release bundle
|
|
427
|
+
* @param {TemporalSignature|object} sig - Signature to verify
|
|
428
|
+
* @returns {{ valid: boolean, error?: string, warnings?: string[] }}
|
|
429
|
+
*/
|
|
430
|
+
verifyFile(bundlePath, sig) {
|
|
431
|
+
const code = readFileSync(bundlePath);
|
|
432
|
+
return this.verify(code, sig);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Save signature to file
|
|
437
|
+
* @param {TemporalSignature} sig
|
|
438
|
+
* @param {string} path
|
|
439
|
+
*/
|
|
440
|
+
saveSignature(sig, path) {
|
|
441
|
+
writeFileSync(path, JSON.stringify(sig.toJSON(), null, 2));
|
|
442
|
+
log.info('Saved signature to file', { path });
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Load signature from file
|
|
447
|
+
* @param {string} path
|
|
448
|
+
* @returns {TemporalSignature}
|
|
449
|
+
*/
|
|
450
|
+
loadSignature(path) {
|
|
451
|
+
const data = JSON.parse(readFileSync(path, 'utf8'));
|
|
452
|
+
return TemporalSignature.fromJSON(data);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Get status
|
|
457
|
+
*/
|
|
458
|
+
getStatus() {
|
|
459
|
+
const sourceStatus = this.#timeSource?.getStatus?.();
|
|
460
|
+
return {
|
|
461
|
+
timeSourceBound: !!this.#timeSource,
|
|
462
|
+
timeSourceType: sourceStatus?.primarySource || 'none',
|
|
463
|
+
networkId: this.#networkId,
|
|
464
|
+
trustedSigners: this.#trustedSigners.size,
|
|
465
|
+
currentTime: this.#getCurrentTime(),
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// =============================================================================
|
|
471
|
+
// SINGLETON & EXPORTS
|
|
472
|
+
// =============================================================================
|
|
473
|
+
|
|
474
|
+
let _instance = null;
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Get the TemporalSigner singleton
|
|
478
|
+
* @param {object} options
|
|
479
|
+
* @returns {TemporalSigner}
|
|
480
|
+
*/
|
|
481
|
+
export function getTemporalSigner(options) {
|
|
482
|
+
if (!_instance) {
|
|
483
|
+
_instance = new TemporalSigner(options);
|
|
484
|
+
}
|
|
485
|
+
return _instance;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export default TemporalSigner;
|