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
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Yakmesh Machine Seed — Deterministic Identity Anchor
|
|
3
|
+
*
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
* TWO-LAYER IDENTITY ARCHITECTURE
|
|
6
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
+
*
|
|
8
|
+
* Layer 1 — NETWORK IDENTITY (shared, code-derived):
|
|
9
|
+
* oracleHash → networkName, networkId, verificationPhrase
|
|
10
|
+
* Every node with byte-identical code arrives at the same network.
|
|
11
|
+
* Machine data is EXCLUDED entirely. This is the peering contract.
|
|
12
|
+
*
|
|
13
|
+
* Layer 2 — NODE IDENTITY (unique, seed-derived):
|
|
14
|
+
* machineSeed + oracleHash + verPhrase → HKDF → ml_dsa65.keygen()
|
|
15
|
+
* The seed creates uniqueness WITHIN the network, not a separate network.
|
|
16
|
+
* If the codebase changes, the same seed produces a DIFFERENT keypair
|
|
17
|
+
* on the new network — the verification phrase acts as domain separator.
|
|
18
|
+
*
|
|
19
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
20
|
+
* SEED STORAGE — HARDWARE-BOUND + YPC-27 INTEGRITY
|
|
21
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
22
|
+
*
|
|
23
|
+
* The seed is NOT stored in plaintext. It is encrypted under a key derived
|
|
24
|
+
* from machine-specific hardware markers (hostname, platform, dataDir).
|
|
25
|
+
* An attacker who copies the seed file to another machine cannot decrypt it.
|
|
26
|
+
*
|
|
27
|
+
* Additionally, a YPC-27 polynomial checksum seals the seed. SIS-hardness
|
|
28
|
+
* (Short Integer Solution in Z[x]/(x^27-1) mod 3) makes it computationally
|
|
29
|
+
* infeasible to craft a different seed that produces the same checksum.
|
|
30
|
+
* This detects both accidental corruption AND deliberate seed injection.
|
|
31
|
+
*
|
|
32
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
33
|
+
* MIGRATION CHAIN — REPUTATION CONTINUITY
|
|
34
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
35
|
+
*
|
|
36
|
+
* When the codebase updates, the same seed produces a new keypair (because
|
|
37
|
+
* the oracle hash changed). The migration chain records each (oracleHash,
|
|
38
|
+
* publicKey) pair so reputation can be cryptographically transferred:
|
|
39
|
+
*
|
|
40
|
+
* migrationProof = sign(oldKey, "MIGRATE:" + newPubKey) +
|
|
41
|
+
* sign(newKey, "MIGRATE:" + oldPubKey)
|
|
42
|
+
*
|
|
43
|
+
* Both signatures together prove the same physical entity controls both
|
|
44
|
+
* identities without revealing the seed.
|
|
45
|
+
*
|
|
46
|
+
* @module identity/machine-seed
|
|
47
|
+
* @version 1.0.0
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
import { createLogger } from '../utils/logger.js';
|
|
51
|
+
|
|
52
|
+
const log = createLogger('identity:machine-seed');
|
|
53
|
+
|
|
54
|
+
import { sha3_256 } from '../utils/accel.js';
|
|
55
|
+
import { bytesToHex, hexToBytes } from '@noble/hashes/utils.js';
|
|
56
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync } from 'fs';
|
|
57
|
+
import { join } from 'path';
|
|
58
|
+
import { createCipheriv, createDecipheriv, scryptSync, randomBytes } from 'crypto';
|
|
59
|
+
import { hostname, platform, cpus } from 'os';
|
|
60
|
+
import { hkdf } from '@noble/hashes/hkdf.js';
|
|
61
|
+
import { sha3_256 as _sha3 } from '@noble/hashes/sha3.js';
|
|
62
|
+
|
|
63
|
+
// YPC-27 polynomial integrity
|
|
64
|
+
import { YPC27Checksum, Poly27, bytesToTrits, DEFAULT_SEED as YPC27_DEFAULT_SEED } from '../oracle/ypc27.js';
|
|
65
|
+
|
|
66
|
+
// SST family analysis
|
|
67
|
+
import { bytesToFamilyTrits, analyzeBytesFamilies } from '../oracle/sst.js';
|
|
68
|
+
|
|
69
|
+
// iO derivation for domain separation
|
|
70
|
+
import { deriveVerificationPhrase } from '../oracle/network-identity.js';
|
|
71
|
+
|
|
72
|
+
// 144T ternary addressing for persistent machine identity
|
|
73
|
+
import { TritAddress, TOTAL_TRITS } from '../oracle/ternary-144t.js';
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
// =============================================================================
|
|
77
|
+
// CONSTANTS
|
|
78
|
+
// =============================================================================
|
|
79
|
+
|
|
80
|
+
/** Seed size in bytes */
|
|
81
|
+
const SEED_BYTES = 32;
|
|
82
|
+
|
|
83
|
+
/** Seed file name */
|
|
84
|
+
const SEED_FILENAME = 'machine-seed.json';
|
|
85
|
+
|
|
86
|
+
/** Current seed file schema version — bumped for persistentId144T */
|
|
87
|
+
const SCHEMA_VERSION = 2;
|
|
88
|
+
|
|
89
|
+
/** Persistent ID prefix */
|
|
90
|
+
const PERSISTENT_ID_PREFIX = 'yak';
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
// =============================================================================
|
|
94
|
+
// HARDWARE FINGERPRINT
|
|
95
|
+
// =============================================================================
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Derive a machine-specific fingerprint from hardware markers.
|
|
99
|
+
* This is used to encrypt the seed at rest — the encrypted seed
|
|
100
|
+
* is useless on any other machine.
|
|
101
|
+
*
|
|
102
|
+
* Uses: hostname, platform, CPU model, dataDir
|
|
103
|
+
* NOT in oracle hash (these are local-only values).
|
|
104
|
+
*
|
|
105
|
+
* @param {string} dataDir - The node's data directory path
|
|
106
|
+
* @returns {Buffer} 32-byte hardware-derived key
|
|
107
|
+
*/
|
|
108
|
+
function deriveHardwareKey(dataDir) {
|
|
109
|
+
const cpuModel = cpus()[0]?.model || 'unknown-cpu';
|
|
110
|
+
const fingerprint = `yakmesh:machine-seed:${hostname()}:${platform()}:${cpuModel}:${dataDir}`;
|
|
111
|
+
const salt = sha3_256(new TextEncoder().encode('yakmesh-hardware-salt-2026'));
|
|
112
|
+
// scrypt: N=2^14, r=8, p=1, 32-byte key
|
|
113
|
+
return scryptSync(fingerprint, Buffer.from(salt), 32, {
|
|
114
|
+
N: 16384, r: 8, p: 1,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
// =============================================================================
|
|
120
|
+
// SEED ENCRYPTION
|
|
121
|
+
// =============================================================================
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Encrypt the seed for at-rest storage using hardware-derived key.
|
|
125
|
+
* AES-256-GCM provides authenticated encryption.
|
|
126
|
+
*
|
|
127
|
+
* @param {Uint8Array} seed - Raw 32-byte seed
|
|
128
|
+
* @param {string} dataDir - For hardware key derivation
|
|
129
|
+
* @returns {{ ciphertext: string, nonce: string, tag: string }}
|
|
130
|
+
*/
|
|
131
|
+
function encryptSeed(seed, dataDir) {
|
|
132
|
+
const key = deriveHardwareKey(dataDir);
|
|
133
|
+
const nonce = randomBytes(12);
|
|
134
|
+
const cipher = createCipheriv('aes-256-gcm', key, nonce);
|
|
135
|
+
const encrypted = Buffer.concat([
|
|
136
|
+
cipher.update(Buffer.from(seed)),
|
|
137
|
+
cipher.final(),
|
|
138
|
+
]);
|
|
139
|
+
return {
|
|
140
|
+
ciphertext: encrypted.toString('hex'),
|
|
141
|
+
nonce: nonce.toString('hex'),
|
|
142
|
+
tag: cipher.getAuthTag().toString('hex'),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Decrypt the seed from at-rest storage.
|
|
148
|
+
*
|
|
149
|
+
* @param {{ ciphertext: string, nonce: string, tag: string }} enc
|
|
150
|
+
* @param {string} dataDir - For hardware key derivation
|
|
151
|
+
* @returns {Uint8Array} Raw 32-byte seed
|
|
152
|
+
*/
|
|
153
|
+
function decryptSeed(enc, dataDir) {
|
|
154
|
+
const key = deriveHardwareKey(dataDir);
|
|
155
|
+
const decipher = createDecipheriv(
|
|
156
|
+
'aes-256-gcm',
|
|
157
|
+
key,
|
|
158
|
+
Buffer.from(enc.nonce, 'hex'),
|
|
159
|
+
);
|
|
160
|
+
decipher.setAuthTag(Buffer.from(enc.tag, 'hex'));
|
|
161
|
+
const decrypted = Buffer.concat([
|
|
162
|
+
decipher.update(Buffer.from(enc.ciphertext, 'hex')),
|
|
163
|
+
decipher.final(),
|
|
164
|
+
]);
|
|
165
|
+
return new Uint8Array(decrypted);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
// =============================================================================
|
|
170
|
+
// YPC-27 INTEGRITY SEAL
|
|
171
|
+
// =============================================================================
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Compute YPC-27 polynomial checksum of the seed.
|
|
175
|
+
* SIS-hard: an attacker cannot craft a different seed with the same checksum.
|
|
176
|
+
*
|
|
177
|
+
* @param {Uint8Array} seed - Raw 32-byte seed
|
|
178
|
+
* @returns {string} Hex-encoded YPC-27 checksum
|
|
179
|
+
*/
|
|
180
|
+
function computeSeedChecksum(seed) {
|
|
181
|
+
const hasher = new YPC27Checksum(YPC27_DEFAULT_SEED);
|
|
182
|
+
hasher.update(seed);
|
|
183
|
+
return hasher.digestHex();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Verify YPC-27 checksum of a seed.
|
|
188
|
+
*
|
|
189
|
+
* @param {Uint8Array} seed - Raw seed bytes
|
|
190
|
+
* @param {string} expectedHex - Expected YPC-27 checksum hex
|
|
191
|
+
* @returns {boolean}
|
|
192
|
+
*/
|
|
193
|
+
function verifySeedChecksum(seed, expectedHex) {
|
|
194
|
+
const computed = computeSeedChecksum(seed);
|
|
195
|
+
return computed === expectedHex;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
// =============================================================================
|
|
200
|
+
// KEYPAIR DERIVATION — DETERMINISTIC FROM SEED + NETWORK
|
|
201
|
+
// =============================================================================
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Derive a deterministic 32-byte secret for ML-DSA-65 keygen.
|
|
205
|
+
*
|
|
206
|
+
* The derivation uses HKDF-SHA3-256 with:
|
|
207
|
+
* ikm = machineSeed (32 bytes, unique per machine)
|
|
208
|
+
* salt = oracleHash (hex string of codebase hash)
|
|
209
|
+
* info = verificationPhrase + ":node-keypair" (domain separator)
|
|
210
|
+
*
|
|
211
|
+
* If the codebase changes (oracleHash changes), the derived secret changes,
|
|
212
|
+
* producing a completely different keypair — even with the same seed.
|
|
213
|
+
* The verification phrase provides additional binding to the network identity.
|
|
214
|
+
*
|
|
215
|
+
* @param {Uint8Array} seed - Raw 32-byte machine seed
|
|
216
|
+
* @param {string} oracleHash - Codebase hash from the validation oracle
|
|
217
|
+
* @param {string} verPhrase - Network verification phrase (from iO)
|
|
218
|
+
* @returns {Uint8Array} 32-byte deterministic secret for ml_dsa65.keygen()
|
|
219
|
+
*/
|
|
220
|
+
export function deriveNodeSecret(seed, oracleHash, verPhrase) {
|
|
221
|
+
if (!seed || seed.length !== SEED_BYTES) {
|
|
222
|
+
throw new Error(`Machine seed must be ${SEED_BYTES} bytes, got ${seed?.length}`);
|
|
223
|
+
}
|
|
224
|
+
if (!oracleHash || typeof oracleHash !== 'string') {
|
|
225
|
+
throw new Error('Oracle hash (codebase hash) is required for key derivation');
|
|
226
|
+
}
|
|
227
|
+
if (!verPhrase || typeof verPhrase !== 'string') {
|
|
228
|
+
throw new Error('Verification phrase is required for key derivation');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const salt = new TextEncoder().encode(oracleHash);
|
|
232
|
+
const info = new TextEncoder().encode(`${verPhrase}:node-keypair`);
|
|
233
|
+
|
|
234
|
+
// HKDF-SHA3-256: one-way derivation, 32-byte output
|
|
235
|
+
return hkdf(_sha3, seed, salt, info, 32);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Derive a deterministic 72-byte secret for SLH-DSA backup keygen.
|
|
240
|
+
* Uses a different info domain separator than the primary key.
|
|
241
|
+
* SLH-DSA-SHA2-192f requires a 72-byte seed (FIPS 205).
|
|
242
|
+
*
|
|
243
|
+
* @param {Uint8Array} seed - Raw 32-byte machine seed
|
|
244
|
+
* @param {string} oracleHash - Codebase hash
|
|
245
|
+
* @param {string} verPhrase - Network verification phrase
|
|
246
|
+
* @returns {Uint8Array} 72-byte deterministic secret for SLH-DSA keygen
|
|
247
|
+
*/
|
|
248
|
+
export function deriveBackupSecret(seed, oracleHash, verPhrase) {
|
|
249
|
+
if (!seed || seed.length !== SEED_BYTES) {
|
|
250
|
+
throw new Error(`Machine seed must be ${SEED_BYTES} bytes`);
|
|
251
|
+
}
|
|
252
|
+
const salt = new TextEncoder().encode(oracleHash);
|
|
253
|
+
const info = new TextEncoder().encode(`${verPhrase}:backup-keypair-slh-dsa`);
|
|
254
|
+
return hkdf(_sha3, seed, salt, info, 72);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
// =============================================================================
|
|
259
|
+
// MIGRATION CHAIN
|
|
260
|
+
// =============================================================================
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Compute persistent machine ID in 144T ternary format.
|
|
264
|
+
* This ID is CONSTANT across all code upgrades — derived directly from seed.
|
|
265
|
+
*
|
|
266
|
+
* Format: "yak-[tier1-144T]" where tier1 is 36 trits (≈57 bits entropy)
|
|
267
|
+
* Example: "yak-TT00TTT00:TTT00TTT0:0TTT00TTT:00TTT00TT"
|
|
268
|
+
*
|
|
269
|
+
* @param {Uint8Array} seed - Raw 32-byte machine seed
|
|
270
|
+
* @returns {string} Persistent 144T identifier
|
|
271
|
+
*/
|
|
272
|
+
function computePersistentId144T(seed) {
|
|
273
|
+
// Domain-separate from other derivations — "persistent-machine-id" context
|
|
274
|
+
const context = new TextEncoder().encode('yakmesh:persistent-machine-id:v1');
|
|
275
|
+
const combined = new Uint8Array(seed.length + context.length);
|
|
276
|
+
combined.set(seed);
|
|
277
|
+
combined.set(context, seed.length);
|
|
278
|
+
|
|
279
|
+
const hash = sha3_256(combined);
|
|
280
|
+
const hex = bytesToHex(hash);
|
|
281
|
+
|
|
282
|
+
// Convert to 144T, extract tier 1 for compact persistent ID
|
|
283
|
+
const tritAddr = TritAddress.fromHex(hex);
|
|
284
|
+
const tier1 = tritAddr.toString().split('.')[0];
|
|
285
|
+
|
|
286
|
+
return `${PERSISTENT_ID_PREFIX}-${tier1}`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Add a migration entry to the chain.
|
|
291
|
+
* Records that this seed derived a specific publicKey under a specific oracleHash.
|
|
292
|
+
*
|
|
293
|
+
* @param {Object[]} chain - Existing migration chain entries
|
|
294
|
+
* @param {string} oracleHash - Current oracle hash
|
|
295
|
+
* @param {string} pubKeyHash - SHA3-256 hash of the public key (not the full key!)
|
|
296
|
+
* @param {string} networkName - Human-readable network name
|
|
297
|
+
* @returns {Object[]} Updated chain
|
|
298
|
+
*/
|
|
299
|
+
function addMigrationEntry(chain, oracleHash, pubKeyHash, networkName) {
|
|
300
|
+
const entry = {
|
|
301
|
+
oracleHash: oracleHash.slice(0, 24), // First 24 chars, enough for lookup
|
|
302
|
+
pubKeyHash: pubKeyHash.slice(0, 32), // First 32 chars
|
|
303
|
+
networkName,
|
|
304
|
+
timestamp: new Date().toISOString(),
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// Don't duplicate — if the same oracleHash already exists, skip
|
|
308
|
+
const existing = chain.find(e => e.oracleHash === entry.oracleHash);
|
|
309
|
+
if (existing) return chain;
|
|
310
|
+
|
|
311
|
+
return [...chain, entry];
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
// =============================================================================
|
|
316
|
+
// SST ANALYSIS (INFORMATIONAL)
|
|
317
|
+
// =============================================================================
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Compute SST family analysis of the seed.
|
|
321
|
+
* Purely informational — stored for display and future trust integration.
|
|
322
|
+
*
|
|
323
|
+
* @param {Uint8Array} seed - Raw seed bytes
|
|
324
|
+
* @returns {{ a: number, b: number, c: number }} Family proportions
|
|
325
|
+
*/
|
|
326
|
+
function analyzeSeedFamilies(seed) {
|
|
327
|
+
return analyzeBytesFamilies(seed);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
// =============================================================================
|
|
332
|
+
// MACHINE SEED MANAGER
|
|
333
|
+
// =============================================================================
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* MachineSeed — Manages the persistent machine seed and its integrity.
|
|
337
|
+
*
|
|
338
|
+
* The seed is the long-term identity anchor. Keypairs are ephemeral
|
|
339
|
+
* projections of the seed onto a specific network version.
|
|
340
|
+
*
|
|
341
|
+
* File stored: machine-seed.json in dataDir
|
|
342
|
+
* Contents: { encryptedSeed, ypc27Checksum, persistentId144T, migrationChain[], sstFamilies, schemaVersion }
|
|
343
|
+
* NEVER stored: raw seed, private keys
|
|
344
|
+
*
|
|
345
|
+
* The persistentId144T is constant across ALL code upgrades — it identifies
|
|
346
|
+
* the physical machine/node owner regardless of which network version is running.
|
|
347
|
+
*/
|
|
348
|
+
export class MachineSeed {
|
|
349
|
+
/**
|
|
350
|
+
* @param {string} dataDir - Data directory (same as NodeIdentity)
|
|
351
|
+
*/
|
|
352
|
+
constructor(dataDir = './data') {
|
|
353
|
+
this.dataDir = dataDir;
|
|
354
|
+
this.seedPath = join(dataDir, SEED_FILENAME);
|
|
355
|
+
this.seed = null; // Raw seed (in memory only)
|
|
356
|
+
this.persistentId = null; // 144T persistent identity (constant across upgrades)
|
|
357
|
+
this.migrationChain = []; // History of (oracleHash, pubKeyHash) pairs
|
|
358
|
+
this.sstFamilies = null; // SST family analysis
|
|
359
|
+
this.created = false; // True if seed was just generated (first run)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Set restrictive file permissions on seed file (owner read/write only).
|
|
364
|
+
* On Windows this is a no-op (NTFS ACLs handle access differently).
|
|
365
|
+
*/
|
|
366
|
+
_secureFile() {
|
|
367
|
+
if (platform() !== 'win32') {
|
|
368
|
+
try {
|
|
369
|
+
chmodSync(this.seedPath, 0o600);
|
|
370
|
+
} catch (e) {
|
|
371
|
+
log.warn('Could not set restrictive permissions on seed file', { error: e.message });
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Initialize the machine seed.
|
|
378
|
+
*
|
|
379
|
+
* - If seed file exists: decrypt, verify YPC-27 checksum, load migration chain
|
|
380
|
+
* - If no seed file: generate random seed, compute checksum, save encrypted
|
|
381
|
+
*
|
|
382
|
+
* @returns {boolean} true if seed loaded/created successfully
|
|
383
|
+
*/
|
|
384
|
+
async init() {
|
|
385
|
+
// Ensure data directory exists
|
|
386
|
+
if (!existsSync(this.dataDir)) {
|
|
387
|
+
mkdirSync(this.dataDir, { recursive: true });
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (existsSync(this.seedPath)) {
|
|
391
|
+
return this._loadExisting();
|
|
392
|
+
} else {
|
|
393
|
+
return this._generateNew();
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Load and verify an existing seed file.
|
|
399
|
+
* @returns {boolean}
|
|
400
|
+
* @private
|
|
401
|
+
*/
|
|
402
|
+
_loadExisting() {
|
|
403
|
+
try {
|
|
404
|
+
const data = JSON.parse(readFileSync(this.seedPath, 'utf8'));
|
|
405
|
+
|
|
406
|
+
// Schema check
|
|
407
|
+
if (data.schemaVersion !== SCHEMA_VERSION) {
|
|
408
|
+
log.warn('Seed file schema version mismatch', {
|
|
409
|
+
expected: SCHEMA_VERSION,
|
|
410
|
+
got: data.schemaVersion,
|
|
411
|
+
});
|
|
412
|
+
// Future: add migration logic for schema upgrades
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Decrypt seed using hardware-derived key
|
|
416
|
+
const seed = decryptSeed(data.encryptedSeed, this.dataDir);
|
|
417
|
+
|
|
418
|
+
// Verify YPC-27 integrity checksum
|
|
419
|
+
if (!verifySeedChecksum(seed, data.ypc27Checksum)) {
|
|
420
|
+
log.error('🚨 YPC-27 SEED INTEGRITY CHECK FAILED — possible tampering!');
|
|
421
|
+
log.error('Seed checksum does not match. The seed file may have been modified.');
|
|
422
|
+
log.error('Refusing to use potentially compromised seed.');
|
|
423
|
+
throw new Error(
|
|
424
|
+
'YPC-27 seed integrity check failed. Seed file may be tampered. ' +
|
|
425
|
+
'Delete machine-seed.json to generate a new identity (reputation will be lost).'
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
this.seed = seed;
|
|
430
|
+
this.persistentId = data.persistentId144T || computePersistentId144T(seed);
|
|
431
|
+
this.migrationChain = data.migrationChain || [];
|
|
432
|
+
this.sstFamilies = data.sstFamilies || analyzeSeedFamilies(seed);
|
|
433
|
+
this.created = false;
|
|
434
|
+
|
|
435
|
+
// Schema migration: add persistentId if missing (v1 → v2)
|
|
436
|
+
if (!data.persistentId144T) {
|
|
437
|
+
log.info('Migrating seed file to v2 (adding persistentId144T)');
|
|
438
|
+
this._updateSeedFile();
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
log.info('Machine seed loaded and verified', {
|
|
442
|
+
ypc27: '✓',
|
|
443
|
+
persistentId: this.persistentId,
|
|
444
|
+
migrationEntries: this.migrationChain.length,
|
|
445
|
+
sstFamilies: this.sstFamilies,
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
return true;
|
|
449
|
+
} catch (e) {
|
|
450
|
+
if (e.message.includes('YPC-27')) throw e; // Re-throw integrity failures
|
|
451
|
+
|
|
452
|
+
log.error('Failed to load machine seed', { error: e.message });
|
|
453
|
+
throw new Error(
|
|
454
|
+
`Cannot load machine seed: ${e.message}. ` +
|
|
455
|
+
'Wrong machine? Seed file is encrypted to the machine that created it.'
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Generate a new random seed, compute integrity seal, save encrypted.
|
|
462
|
+
* @returns {boolean}
|
|
463
|
+
* @private
|
|
464
|
+
*/
|
|
465
|
+
_generateNew() {
|
|
466
|
+
log.info('Generating new machine seed (first run on this machine)');
|
|
467
|
+
|
|
468
|
+
// Cryptographically random 32-byte seed
|
|
469
|
+
const seed = new Uint8Array(randomBytes(SEED_BYTES));
|
|
470
|
+
|
|
471
|
+
// Compute YPC-27 integrity checksum (SIS-hard polynomial seal)
|
|
472
|
+
const ypc27Checksum = computeSeedChecksum(seed);
|
|
473
|
+
|
|
474
|
+
// Compute persistent 144T identity (constant across all upgrades)
|
|
475
|
+
const persistentId144T = computePersistentId144T(seed);
|
|
476
|
+
|
|
477
|
+
// Compute SST family analysis
|
|
478
|
+
const sstFamilies = analyzeSeedFamilies(seed);
|
|
479
|
+
|
|
480
|
+
// Encrypt seed under hardware-derived key
|
|
481
|
+
const encryptedSeed = encryptSeed(seed, this.dataDir);
|
|
482
|
+
|
|
483
|
+
// Save seed file
|
|
484
|
+
const seedFile = {
|
|
485
|
+
schemaVersion: SCHEMA_VERSION,
|
|
486
|
+
encryptedSeed,
|
|
487
|
+
ypc27Checksum,
|
|
488
|
+
persistentId144T,
|
|
489
|
+
sstFamilies,
|
|
490
|
+
migrationChain: [],
|
|
491
|
+
createdAt: new Date().toISOString(),
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
writeFileSync(this.seedPath, JSON.stringify(seedFile, null, 2));
|
|
495
|
+
this._secureFile();
|
|
496
|
+
|
|
497
|
+
this.seed = seed;
|
|
498
|
+
this.persistentId = persistentId144T;
|
|
499
|
+
this.migrationChain = [];
|
|
500
|
+
this.sstFamilies = sstFamilies;
|
|
501
|
+
this.created = true;
|
|
502
|
+
|
|
503
|
+
log.info('Machine seed generated and secured', {
|
|
504
|
+
persistentId: persistentId144T,
|
|
505
|
+
ypc27Checksum: ypc27Checksum.slice(0, 12) + '...',
|
|
506
|
+
sstFamilies,
|
|
507
|
+
encrypted: true,
|
|
508
|
+
hardwareBound: true,
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
return true;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Record a migration entry after deriving a keypair.
|
|
516
|
+
*
|
|
517
|
+
* @param {string} oracleHash - Current oracle hash
|
|
518
|
+
* @param {string} pubKeyHash - SHA3-256(publicKey) hex
|
|
519
|
+
* @param {string} networkName - Human-readable network name
|
|
520
|
+
*/
|
|
521
|
+
recordMigration(oracleHash, pubKeyHash, networkName) {
|
|
522
|
+
this.migrationChain = addMigrationEntry(
|
|
523
|
+
this.migrationChain, oracleHash, pubKeyHash, networkName
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
// Update seed file with new migration entry
|
|
527
|
+
this._updateSeedFile();
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Get the previous identity on the migration chain (for migration proofs).
|
|
532
|
+
*
|
|
533
|
+
* @returns {{ oracleHash: string, pubKeyHash: string, networkName: string, timestamp: string } | null}
|
|
534
|
+
*/
|
|
535
|
+
getPreviousIdentity() {
|
|
536
|
+
if (this.migrationChain.length < 2) return null;
|
|
537
|
+
return this.migrationChain[this.migrationChain.length - 2];
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Get the full migration chain for reputation lookup.
|
|
542
|
+
* @returns {Object[]}
|
|
543
|
+
*/
|
|
544
|
+
getMigrationChain() {
|
|
545
|
+
return [...this.migrationChain];
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Check if this seed has been used on a previous network version.
|
|
550
|
+
* @param {string} oracleHash - Current oracle hash
|
|
551
|
+
* @returns {boolean}
|
|
552
|
+
*/
|
|
553
|
+
hasNetworkHistory(oracleHash) {
|
|
554
|
+
const currentEntry = this.migrationChain.find(
|
|
555
|
+
e => e.oracleHash === oracleHash.slice(0, 24)
|
|
556
|
+
);
|
|
557
|
+
return this.migrationChain.length > 0 && !currentEntry;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Update the seed file on disk (for migration chain updates).
|
|
562
|
+
* Re-encrypts the seed and updates all metadata.
|
|
563
|
+
* @private
|
|
564
|
+
*/
|
|
565
|
+
_updateSeedFile() {
|
|
566
|
+
if (!this.seed) {
|
|
567
|
+
log.error('Cannot update seed file: seed not loaded');
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const encryptedSeed = encryptSeed(this.seed, this.dataDir);
|
|
572
|
+
const ypc27Checksum = computeSeedChecksum(this.seed);
|
|
573
|
+
const persistentId144T = this.persistentId || computePersistentId144T(this.seed);
|
|
574
|
+
const sstFamilies = this.sstFamilies || analyzeSeedFamilies(this.seed);
|
|
575
|
+
|
|
576
|
+
const existing = existsSync(this.seedPath)
|
|
577
|
+
? JSON.parse(readFileSync(this.seedPath, 'utf8'))
|
|
578
|
+
: {};
|
|
579
|
+
|
|
580
|
+
const seedFile = {
|
|
581
|
+
schemaVersion: SCHEMA_VERSION,
|
|
582
|
+
encryptedSeed,
|
|
583
|
+
ypc27Checksum,
|
|
584
|
+
persistentId144T,
|
|
585
|
+
sstFamilies,
|
|
586
|
+
migrationChain: this.migrationChain,
|
|
587
|
+
createdAt: existing.createdAt || new Date().toISOString(),
|
|
588
|
+
updatedAt: new Date().toISOString(),
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
writeFileSync(this.seedPath, JSON.stringify(seedFile, null, 2));
|
|
592
|
+
this._secureFile();
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Get raw seed bytes (for key derivation — NEVER log or transmit).
|
|
597
|
+
* @returns {Uint8Array}
|
|
598
|
+
*/
|
|
599
|
+
getSeed() {
|
|
600
|
+
if (!this.seed) {
|
|
601
|
+
throw new Error('Machine seed not initialized. Call init() first.');
|
|
602
|
+
}
|
|
603
|
+
return this.seed;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Get the persistent 144T machine identity.
|
|
608
|
+
* This ID is CONSTANT across all code upgrades — it identifies the
|
|
609
|
+
* physical machine/node owner regardless of network version.
|
|
610
|
+
*
|
|
611
|
+
* Format: "yak-[tier1-144T]"
|
|
612
|
+
* Example: "yak-TT00TTT00:TTT00TTT0:0TTT00TTT:00TTT00TT"
|
|
613
|
+
*
|
|
614
|
+
* @returns {string} Persistent 144T identifier
|
|
615
|
+
*/
|
|
616
|
+
getPersistentId() {
|
|
617
|
+
if (!this.persistentId) {
|
|
618
|
+
throw new Error('Machine seed not initialized. Call init() first.');
|
|
619
|
+
}
|
|
620
|
+
return this.persistentId;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Check if seed was just created (first run).
|
|
625
|
+
* @returns {boolean}
|
|
626
|
+
*/
|
|
627
|
+
isFirstRun() {
|
|
628
|
+
return this.created;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
export default MachineSeed;
|