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.
Files changed (232) hide show
  1. package/CHANGELOG.md +637 -0
  2. package/CONTRIBUTING.md +42 -0
  3. package/Caddyfile +77 -0
  4. package/README.md +119 -29
  5. package/adapters/adapter-mlv-bible/README.md +124 -0
  6. package/adapters/adapter-mlv-bible/index.js +400 -0
  7. package/adapters/chat-mod-adapter.js +532 -0
  8. package/adapters/content-adapter.js +273 -0
  9. package/content/api.js +50 -41
  10. package/content/index.js +2 -2
  11. package/content/store.js +355 -173
  12. package/dashboard/index.html +19 -3
  13. package/database/replication.js +117 -37
  14. package/docs/CRYPTO-AGILITY.md +204 -0
  15. package/docs/MTLS-RESEARCH.md +367 -0
  16. package/docs/NAMCHE-SPEC.md +681 -0
  17. package/docs/PEERQUANTA-YAKMESH-INTEGRATION.md +407 -0
  18. package/docs/PRECISION-DISCLOSURE.md +96 -0
  19. package/docs/README.md +76 -0
  20. package/docs/ROADMAP-2.4.0.md +447 -0
  21. package/docs/ROADMAP-2.5.0.md +244 -0
  22. package/docs/SECURITY-AUDIT-REPORT.md +306 -0
  23. package/docs/SST-INTEGRATION.md +712 -0
  24. package/docs/STEADYWATCH-IMPLEMENTATION.md +303 -0
  25. package/docs/TERNARY-AUDIT-REPORT.md +247 -0
  26. package/docs/TME-FAQ.md +221 -0
  27. package/docs/WHITEPAPER.md +623 -0
  28. package/docs/adapters.html +1001 -0
  29. package/docs/advanced-systems.html +1045 -0
  30. package/docs/annex.html +1046 -0
  31. package/docs/api.html +970 -0
  32. package/docs/business/response-templates.md +160 -0
  33. package/docs/c2c.html +1225 -0
  34. package/docs/cli.html +1332 -0
  35. package/docs/configuration.html +1248 -0
  36. package/docs/darshan.html +1085 -0
  37. package/docs/dharma.html +966 -0
  38. package/docs/docs-bundle.html +1075 -0
  39. package/docs/docs.css +3120 -0
  40. package/docs/docs.js +556 -0
  41. package/docs/doko.html +969 -0
  42. package/docs/geo-proof.html +858 -0
  43. package/docs/getting-started.html +840 -0
  44. package/docs/gumba-tutorial.html +1144 -0
  45. package/docs/gumba.html +1098 -0
  46. package/docs/index.html +914 -0
  47. package/docs/jhilke.html +1312 -0
  48. package/docs/karma.html +1100 -0
  49. package/docs/katha.html +1037 -0
  50. package/docs/lama.html +978 -0
  51. package/docs/mandala.html +1067 -0
  52. package/docs/mani.html +964 -0
  53. package/docs/mantra.html +967 -0
  54. package/docs/mesh.html +1409 -0
  55. package/docs/nakpak.html +869 -0
  56. package/docs/namche.html +928 -0
  57. package/docs/nav-order.json +53 -0
  58. package/docs/prahari.html +1043 -0
  59. package/docs/prism-bash.min.js +1 -0
  60. package/docs/prism-javascript.min.js +1 -0
  61. package/docs/prism-json.min.js +1 -0
  62. package/docs/prism-tomorrow.min.css +1 -0
  63. package/docs/prism.min.js +1 -0
  64. package/docs/privacy.html +699 -0
  65. package/docs/quick-reference.html +1181 -0
  66. package/docs/sakshi.html +1402 -0
  67. package/docs/sandboxing.md +386 -0
  68. package/docs/seva.html +911 -0
  69. package/docs/sherpa.html +871 -0
  70. package/docs/studio.html +860 -0
  71. package/docs/stupa.html +995 -0
  72. package/docs/tailwind.min.css +2 -0
  73. package/docs/tattva.html +1332 -0
  74. package/docs/terms.html +686 -0
  75. package/docs/time-server-deployment.md +166 -0
  76. package/docs/time-sources.html +1392 -0
  77. package/docs/tivra.html +1127 -0
  78. package/docs/trademark-policy.html +686 -0
  79. package/docs/tribhuj.html +1183 -0
  80. package/docs/trust-security.html +1029 -0
  81. package/docs/tutorials/backup-recovery.html +654 -0
  82. package/docs/tutorials/dashboard.html +604 -0
  83. package/docs/tutorials/domain-setup.html +605 -0
  84. package/docs/tutorials/host-website.html +456 -0
  85. package/docs/tutorials/mesh-network.html +505 -0
  86. package/docs/tutorials/mobile-access.html +445 -0
  87. package/docs/tutorials/privacy.html +467 -0
  88. package/docs/tutorials/raspberry-pi.html +600 -0
  89. package/docs/tutorials/security-basics.html +539 -0
  90. package/docs/tutorials/share-files.html +431 -0
  91. package/docs/tutorials/troubleshooting.html +637 -0
  92. package/docs/tutorials/trust-karma.html +419 -0
  93. package/docs/tutorials/yak-protocol.html +456 -0
  94. package/docs/tutorials.html +1034 -0
  95. package/docs/vani.html +1270 -0
  96. package/docs/webserver.html +809 -0
  97. package/docs/yak-protocol.html +940 -0
  98. package/docs/yak-timeserver-design.md +475 -0
  99. package/docs/yakapp.html +1015 -0
  100. package/docs/ypc27.html +1069 -0
  101. package/docs/yurt.html +1344 -0
  102. package/embedded-docs/bundle.js +334 -74
  103. package/gossip/protocol.js +247 -27
  104. package/identity/key-resolver.js +262 -0
  105. package/identity/machine-seed.js +632 -0
  106. package/identity/node-key.js +669 -368
  107. package/identity/tribhuj-ratchet.js +506 -0
  108. package/knowledge-base.js +37 -8
  109. package/launcher/yakmesh.bat +62 -0
  110. package/launcher/yakmesh.sh +70 -0
  111. package/mesh/annex.js +462 -108
  112. package/mesh/beacon-broadcast.js +113 -1
  113. package/mesh/darshan.js +1718 -0
  114. package/mesh/gumba.js +1567 -0
  115. package/mesh/jhilke.js +651 -0
  116. package/mesh/katha.js +1012 -0
  117. package/mesh/nakpak-routing.js +8 -5
  118. package/mesh/network.js +724 -34
  119. package/mesh/pulse-sync.js +4 -1
  120. package/mesh/rate-limiter.js +127 -15
  121. package/mesh/seva.js +526 -0
  122. package/mesh/sherpa-discovery.js +89 -8
  123. package/mesh/sybil-defense.js +19 -5
  124. package/mesh/temporal-encoder.js +4 -3
  125. package/mesh/vani.js +1364 -0
  126. package/mesh/yurt.js +1340 -0
  127. package/models/entropy-sentinel.onnx +0 -0
  128. package/models/karma-trust.onnx +0 -0
  129. package/models/manifest.json +43 -0
  130. package/models/sakshi-anomaly.onnx +0 -0
  131. package/oracle/code-proof-protocol.js +7 -6
  132. package/oracle/codebase-lock.js +257 -28
  133. package/oracle/index.js +74 -15
  134. package/oracle/ma902-snmp.js +678 -0
  135. package/oracle/module-sealer.js +5 -3
  136. package/oracle/network-identity.js +16 -0
  137. package/oracle/packet-checksum.js +201 -0
  138. package/oracle/sst.js +579 -0
  139. package/oracle/ternary-144t.js +714 -0
  140. package/oracle/ternary-ml.js +481 -0
  141. package/oracle/time-api.js +239 -0
  142. package/oracle/time-source.js +137 -47
  143. package/oracle/validation-oracle-hardened.js +1111 -1071
  144. package/oracle/validation-oracle.js +4 -2
  145. package/oracle/ypc27.js +211 -0
  146. package/package.json +20 -3
  147. package/protocol/yak-handler.js +35 -9
  148. package/protocol/yak-protocol.js +28 -13
  149. package/reference/cpp/yakmesh_mceliece_shard.cpp +168 -0
  150. package/reference/cpp/yakmesh_ypc27.cpp +179 -0
  151. package/sbom.json +87 -0
  152. package/scripts/security-audit.mjs +264 -0
  153. package/scripts/update-docs-nav.js +194 -0
  154. package/scripts/update-docs-sidebar.cjs +164 -0
  155. package/security/crypto-config.js +4 -3
  156. package/security/dharma-moderation.js +517 -0
  157. package/security/doko-identity.js +193 -143
  158. package/security/domain-consensus.js +86 -85
  159. package/security/fs-hardening.js +620 -0
  160. package/security/hardware-attestation.js +5 -3
  161. package/security/hybrid-trust.js +227 -87
  162. package/security/karma-rate-limiter.js +692 -0
  163. package/security/khata-protocol.js +22 -21
  164. package/security/khata-trust-integration.js +277 -150
  165. package/security/memory-safety.js +635 -0
  166. package/security/mesh-auth.js +11 -10
  167. package/security/mesh-revocation.js +373 -5
  168. package/security/namche-gateway.js +298 -69
  169. package/security/sakshi.js +460 -3
  170. package/security/sangha.js +770 -0
  171. package/security/secure-config.js +473 -0
  172. package/security/silicon-parity.js +13 -10
  173. package/security/steadywatch.js +1142 -0
  174. package/security/strike-system.js +32 -3
  175. package/security/temporal-signing.js +488 -0
  176. package/security/trit-commitment.js +464 -0
  177. package/server/crypto/annex.js +247 -0
  178. package/server/darshan-api.js +343 -0
  179. package/server/index.js +3259 -362
  180. package/server/komm-api.js +668 -0
  181. package/utils/accel.js +2273 -0
  182. package/utils/ternary-id.js +79 -0
  183. package/utils/verify-worker.js +57 -0
  184. package/webserver/index.js +95 -5
  185. package/assets/yakmesh-logo.png +0 -0
  186. package/assets/yakmesh-logo.svg +0 -80
  187. package/assets/yakmesh-logo2.png +0 -0
  188. package/assets/yakmesh-logo2sm.png +0 -0
  189. package/assets/ymsm.png +0 -0
  190. package/website/assets/silhouettes/adapters.svg +0 -107
  191. package/website/assets/silhouettes/api-endpoints.svg +0 -115
  192. package/website/assets/silhouettes/atomic-clock.svg +0 -83
  193. package/website/assets/silhouettes/base-camp.svg +0 -81
  194. package/website/assets/silhouettes/bridge.svg +0 -69
  195. package/website/assets/silhouettes/docs-bundle.svg +0 -113
  196. package/website/assets/silhouettes/doko-basket.svg +0 -70
  197. package/website/assets/silhouettes/fortress.svg +0 -93
  198. package/website/assets/silhouettes/gateway.svg +0 -54
  199. package/website/assets/silhouettes/gears.svg +0 -93
  200. package/website/assets/silhouettes/globe-satellite.svg +0 -67
  201. package/website/assets/silhouettes/karma-wheel.svg +0 -137
  202. package/website/assets/silhouettes/lama-council.svg +0 -141
  203. package/website/assets/silhouettes/mandala-network.svg +0 -169
  204. package/website/assets/silhouettes/mani-stones.svg +0 -149
  205. package/website/assets/silhouettes/mantra-wheel.svg +0 -116
  206. package/website/assets/silhouettes/mesh-nodes.svg +0 -113
  207. package/website/assets/silhouettes/nakpak.svg +0 -56
  208. package/website/assets/silhouettes/peak-lightning.svg +0 -73
  209. package/website/assets/silhouettes/sherpa.svg +0 -69
  210. package/website/assets/silhouettes/stupa-tower.svg +0 -119
  211. package/website/assets/silhouettes/tattva-eye.svg +0 -78
  212. package/website/assets/silhouettes/terminal.svg +0 -74
  213. package/website/assets/silhouettes/webserver.svg +0 -145
  214. package/website/assets/silhouettes/yak.svg +0 -78
  215. package/website/assets/yakmesh-logo.png +0 -0
  216. package/website/assets/yakmesh-logo.webp +0 -0
  217. package/website/assets/yakmesh-logo128x140.webp +0 -0
  218. package/website/assets/yakmesh-logo2.png +0 -0
  219. package/website/assets/yakmesh-logo2.svg +0 -51
  220. package/website/assets/yakmesh-logo40x44.webp +0 -0
  221. package/website/assets/yakmesh.gif +0 -0
  222. package/website/assets/yakmesh.ico +0 -0
  223. package/website/assets/yakmesh.jpg +0 -0
  224. package/website/assets/yakmesh.pdf +0 -0
  225. package/website/assets/yakmesh.png +0 -0
  226. package/website/assets/yakmesh.svg +0 -70
  227. package/website/assets/yakmesh128.webp +0 -0
  228. package/website/assets/yakmesh32.png +0 -0
  229. package/website/assets/yakmesh32.svg +0 -65
  230. package/website/assets/yakmesh32o.ico +0 -2
  231. package/website/assets/yakmesh32o.svg +0 -65
  232. 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 = false;
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 = true;
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;