forge-trust-chain 0.3.0 → 0.5.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/src/cli/index.js CHANGED
@@ -18,10 +18,24 @@ import { scan, formatScanResults } from "../scanner/index.js";
18
18
  import { TrustChain, findDivergence } from "../core/chain.js";
19
19
  import { Store } from "../store/store.js";
20
20
  import { hash } from "../core/trust-pixel.js";
21
- import { verifyAtom, formatAtom, createAtom } from "../core/trust-atom.js";
21
+ import { verifyAtom, formatAtom, createAtom, createSignedAtom, verifyAtomSignature, verifyChain } from "../core/trust-atom.js";
22
+ import { initializeKeys, loadKeys, hasKeys, exportIdentity as exportKeysIdentity, importTrustedKey, listTrustedKeys, getFingerprint } from "../core/keys.js";
22
23
  import { execSync } from "node:child_process";
23
24
  import { hostname, userInfo } from "node:os";
24
25
  import { submitToOTS, checkOTSUpgrade, witnessSummary, createBilateralWitness, witnessLevel } from "../core/witness.js";
26
+ import {
27
+ createGenesis,
28
+ registerInstance,
29
+ loadGenesis,
30
+ loadCurrentInstance,
31
+ loadInstances,
32
+ verifyIdentityChain,
33
+ exportIdentity,
34
+ getForgeIdentity,
35
+ hasForgeId,
36
+ hasInstance,
37
+ signAtomProof,
38
+ } from "../core/identity.js";
25
39
 
26
40
  /* ================================================================
27
41
  STATE SNAPSHOT — captures system state for from/to fields
@@ -46,6 +60,11 @@ function stateSnapshot() {
46
60
  }
47
61
 
48
62
  function getIdentity() {
63
+ // Use ForgeID when initialized and instance registered
64
+ const forgeIdentity = getForgeIdentity();
65
+ if (forgeIdentity !== null) return forgeIdentity;
66
+
67
+ // Fallback to legacy identity
49
68
  try {
50
69
  const user = userInfo().username;
51
70
  const host = hostname();
@@ -59,6 +78,134 @@ function getIdentity() {
59
78
  COMMANDS
60
79
  ================================================================ */
61
80
 
81
+ function cmdInit(name) {
82
+ console.log("");
83
+ console.log(" ── FORGE Identity Initialization ──");
84
+ console.log("");
85
+
86
+ if (hasKeys()) {
87
+ const keys = loadKeys();
88
+ console.log(" ⚠️ Keys already exist!");
89
+ console.log(` Fingerprint: ${keys.fingerprint}`);
90
+ console.log("");
91
+ console.log(" To regenerate (WARNING: invalidates all your signed atoms):");
92
+ console.log(" rm -rf ~/.forge/keys && forge init");
93
+ console.log("");
94
+ return;
95
+ }
96
+
97
+ const identity = {
98
+ name: name || getIdentity(),
99
+ created_by: "forge-cli",
100
+ };
101
+
102
+ try {
103
+ const result = initializeKeys(identity);
104
+
105
+ console.log(" ✓ Ed25519 key pair generated");
106
+ console.log("");
107
+ console.log(` Fingerprint: ${result.fingerprint}`);
108
+ console.log(` Identity: ${identity.name}`);
109
+ console.log("");
110
+ console.log(" Files created:");
111
+ console.log(" ~/.forge/keys/private.key (NEVER share this!)");
112
+ console.log(" ~/.forge/keys/public.key (share for verification)");
113
+ console.log("");
114
+ console.log(" From now on, all atoms will be signed with your private key.");
115
+ console.log(" Others can verify your atoms using your public key.");
116
+ console.log("");
117
+ console.log(" Share your identity:");
118
+ console.log(" forge identity --export > my-identity.json");
119
+ console.log("");
120
+ } catch (err) {
121
+ console.error(` ✗ Error: ${err.message}`);
122
+ process.exit(1);
123
+ }
124
+ }
125
+
126
+ function cmdIdentity(subArgs) {
127
+ if (subArgs.includes("--export")) {
128
+ const identity = exportKeysIdentity();
129
+ if (!identity) {
130
+ console.error(" No identity found. Run 'forge init' first.");
131
+ process.exit(1);
132
+ }
133
+ console.log(JSON.stringify(identity, null, 2));
134
+ return;
135
+ }
136
+
137
+ if (subArgs.includes("--import")) {
138
+ const filePath = subArgs[subArgs.indexOf("--import") + 1];
139
+ if (!filePath) {
140
+ console.error(" Usage: forge identity --import <file.json> [--alias <name>]");
141
+ process.exit(1);
142
+ }
143
+
144
+ try {
145
+ const { readFileSync } = require("node:fs");
146
+ const data = JSON.parse(readFileSync(filePath, "utf8"));
147
+ const alias = subArgs.includes("--alias")
148
+ ? subArgs[subArgs.indexOf("--alias") + 1]
149
+ : data.identity?.name || null;
150
+
151
+ const result = importTrustedKey(data.public_key, alias);
152
+ console.log("");
153
+ console.log(" ✓ Trusted identity imported");
154
+ console.log(` Fingerprint: ${result.fingerprint}`);
155
+ console.log(` Alias: ${result.alias || "(none)"}`);
156
+ console.log("");
157
+ } catch (err) {
158
+ console.error(` ✗ Error: ${err.message}`);
159
+ process.exit(1);
160
+ }
161
+ return;
162
+ }
163
+
164
+ if (subArgs.includes("--list")) {
165
+ const trusted = listTrustedKeys();
166
+ console.log("");
167
+ console.log(" ── Trusted Identities ──");
168
+ console.log("");
169
+
170
+ if (trusted.length === 0) {
171
+ console.log(" No trusted identities. Import with:");
172
+ console.log(" forge identity --import <file.json>");
173
+ } else {
174
+ for (const t of trusted) {
175
+ console.log(` ${t.fingerprint}`);
176
+ if (t.alias) console.log(` Alias: ${t.alias}`);
177
+ console.log(` Imported: ${new Date(t.imported_at).toISOString()}`);
178
+ console.log("");
179
+ }
180
+ }
181
+ return;
182
+ }
183
+
184
+ // Show current identity
185
+ if (!hasKeys()) {
186
+ console.log("");
187
+ console.log(" No identity found. Create one with:");
188
+ console.log(" forge init");
189
+ console.log("");
190
+ return;
191
+ }
192
+
193
+ const keys = loadKeys();
194
+ console.log("");
195
+ console.log(" ── Your FORGE Identity ──");
196
+ console.log("");
197
+ console.log(` Fingerprint: ${keys.fingerprint}`);
198
+ console.log(` Name: ${keys.identity?.name || "(not set)"}`);
199
+ console.log(` Created: ${new Date(keys.identity?.created_at).toISOString()}`);
200
+ console.log(` Algorithm: Ed25519`);
201
+ console.log("");
202
+ console.log(" Commands:");
203
+ console.log(" forge identity --export Export public key for sharing");
204
+ console.log(" forge identity --import Import trusted identity");
205
+ console.log(" forge identity --list List trusted identities");
206
+ console.log("");
207
+ }
208
+
62
209
  function cmdScan() {
63
210
  const results = scan();
64
211
  console.log(formatScanResults(results));
@@ -78,24 +225,65 @@ function cmdLog(action) {
78
225
 
79
226
  const prev = store.lastProof();
80
227
 
81
- const atom = createAtom({
82
- who: identity,
83
- from: stateBefore,
84
- action,
85
- to: stateSnapshot(),
86
- prev,
87
- });
228
+ // Signing priority: ForgeID instance > keys.js > unsigned
229
+ let atom;
230
+ let signed = false;
231
+ let signerInfo = null;
232
+
233
+ if (hasInstance()) {
234
+ atom = createAtom({
235
+ who: identity,
236
+ from: stateBefore,
237
+ action,
238
+ to: stateSnapshot(),
239
+ prev,
240
+ sign: signAtomProof,
241
+ });
242
+ signed = !!atom.signature;
243
+ signerInfo = "ForgeID";
244
+ } else {
245
+ const keys = loadKeys();
246
+ if (keys) {
247
+ atom = createSignedAtom({
248
+ who: identity,
249
+ from: stateBefore,
250
+ action,
251
+ to: stateSnapshot(),
252
+ prev,
253
+ });
254
+ signed = true;
255
+ signerInfo = keys.fingerprint;
256
+ } else {
257
+ atom = createAtom({
258
+ who: identity,
259
+ from: stateBefore,
260
+ action,
261
+ to: stateSnapshot(),
262
+ prev,
263
+ });
264
+ }
265
+ }
88
266
 
89
267
  const index = store.appendAtom(atom);
90
268
 
269
+ // Save plaintext action to local index (never exported)
270
+ store.saveAction(atom.action, action, { who: identity });
271
+
91
272
  console.log("");
92
273
  console.log(" ✓ TrustAtom recorded");
93
274
  console.log(` Index: #${index}`);
94
- console.log(` Who: ${identity} → ${atom.who.slice(0, 16)}…`);
275
+ console.log(` Who: ${identity.length > 32 ? identity.slice(0, 16) + "…" : identity} → ${atom.who.slice(0, 16)}…`);
95
276
  console.log(` Action: ${action}`);
96
277
  console.log(` Proof: ${atom.proof.slice(0, 32)}…`);
97
278
  console.log(` Chain: ${store.atomCount} atoms total`);
98
279
  console.log(` Prev: ${prev === "genesis" ? "genesis" : prev.slice(0, 16) + "…"}`);
280
+
281
+ if (signed) {
282
+ console.log(` Signed: ✓ ${signerInfo}`);
283
+ } else {
284
+ console.log(` Signed: ✗ (run 'forge init' or 'forge id init' to enable signing)`);
285
+ }
286
+
99
287
  console.log("");
100
288
  console.log(" Witness: self (local only)");
101
289
  console.log(" → Run 'forge seal' to create Merkle block");
@@ -115,25 +303,40 @@ function cmdVerify() {
115
303
  console.log(" Verifying chain integrity…");
116
304
  console.log(` Atoms: ${atoms.length}`);
117
305
 
118
- // Verify each atom
119
- let broken = -1;
120
- for (let i = 0; i < atoms.length; i++) {
121
- if (!verifyAtom(atoms[i])) {
122
- broken = i;
123
- break;
124
- }
125
- if (i > 0 && !atoms[i].prev.includes(atoms[i - 1].proof)) {
126
- broken = i;
127
- break;
128
- }
129
- }
306
+ // Use new verifyChain with signature support
307
+ const result = verifyChain(atoms);
130
308
 
131
- if (broken >= 0) {
132
- console.log(` ✗ CHAIN BROKEN at atom #${broken}`);
309
+ if (!result.valid) {
310
+ console.log(` ✗ CHAIN BROKEN at atom #${result.broken_at}`);
311
+ console.log(` Reason: ${result.reason}`);
133
312
  console.log(` This means records have been tampered with.`);
134
313
  } else {
135
314
  console.log(" ✓ CHAIN VALID — all atoms verified, all links intact.");
136
315
  }
316
+
317
+ // Signature summary
318
+ const signed = atoms.filter(a => a.signature).length;
319
+ const unsigned = atoms.length - signed;
320
+
321
+ console.log("");
322
+ console.log(" Signature status:");
323
+ console.log(` Signed: ${signed} atoms`);
324
+ console.log(` Unsigned: ${unsigned} atoms`);
325
+
326
+ if (result.signatures && result.signatures.length > 0) {
327
+ const valid = result.signatures.filter(s => s.valid).length;
328
+ const invalid = result.signatures.filter(s => !s.valid).length;
329
+
330
+ if (invalid > 0) {
331
+ console.log(` ⚠️ ${invalid} signature(s) could not be verified`);
332
+ for (const sig of result.signatures.filter(s => !s.valid)) {
333
+ console.log(` #${sig.index}: ${sig.reason}${sig.signer ? ` (${sig.signer})` : ""}`);
334
+ }
335
+ } else if (valid > 0) {
336
+ console.log(` ✓ All ${valid} signature(s) verified`);
337
+ }
338
+ }
339
+
137
340
  console.log("");
138
341
  }
139
342
 
@@ -177,7 +380,19 @@ function cmdStatus() {
177
380
 
178
381
  console.log("");
179
382
  console.log(" ── FORGE Chain Status ──");
180
- console.log(` Identity: ${getIdentity()}`);
383
+
384
+ const genesis = loadGenesis();
385
+ if (genesis) {
386
+ console.log(` ForgeID: ${genesis.forge_id.slice(0, 16)}…`);
387
+ const inst = loadCurrentInstance();
388
+ if (inst) {
389
+ console.log(` Instance: #${inst.instance_seq} (${inst.label})`);
390
+ } else {
391
+ console.log(` Instance: not registered on this server`);
392
+ }
393
+ } else {
394
+ console.log(` Identity: ${getIdentity()} (legacy — run 'forge id init' for ForgeID)`);
395
+ }
181
396
  console.log(` Atoms: ${atoms.length}`);
182
397
  console.log(` Blocks: ${blocks.length}`);
183
398
  console.log(` Store: ~/.forge/chain.json`);
@@ -307,6 +522,29 @@ function cmdWitness(subArgs) {
307
522
  }
308
523
  }
309
524
 
525
+ function cmdHistory(limit = 20) {
526
+ const store = new Store();
527
+ const history = store.getHistory(parseInt(limit) || 20);
528
+
529
+ if (history.length === 0) {
530
+ console.log("\n No history. Run 'forge log' first.\n");
531
+ return;
532
+ }
533
+
534
+ console.log("");
535
+ console.log(" ── FORGE History (local plaintext) ──");
536
+ console.log(" ⚠️ This data is LOCAL ONLY. Never share actions.json.");
537
+ console.log("");
538
+
539
+ for (const entry of history) {
540
+ const time = new Date(entry.when).toISOString().slice(0, 19);
541
+ console.log(` #${entry.index} [${time}]`);
542
+ console.log(` ${entry.action_text}`);
543
+ console.log(` proof: ${entry.proof.slice(0, 24)}…`);
544
+ console.log("");
545
+ }
546
+ }
547
+
310
548
  function cmdDemo() {
311
549
  console.log("");
312
550
  console.log("╔══════════════════════════════════════════════════════╗");
@@ -463,6 +701,165 @@ function cmdDemo() {
463
701
  console.log("");
464
702
  }
465
703
 
704
+ /* ================================================================
705
+ IDENTITY COMMANDS
706
+ ================================================================ */
707
+
708
+ function cmdIdInit(args) {
709
+ const proofIdx = args.indexOf("--proof");
710
+ const proofText = proofIdx >= 0 ? args.slice(proofIdx + 1).join(" ") : "";
711
+
712
+ try {
713
+ const genesis = createGenesis(proofText);
714
+ console.log("");
715
+ console.log(" ✓ ForgeID Genesis Created");
716
+ console.log(` ForgeID: ${genesis.forge_id}`);
717
+ console.log(` Master Key: ${genesis.master_public_key.slice(0, 24)}…`);
718
+ if (proofText) console.log(` PoE: ${proofText}`);
719
+ console.log(` Created: ${genesis.created_at}`);
720
+ console.log("");
721
+ console.log(" ⚠️ ~/.forge/identity/master.key contains your master private key.");
722
+ console.log(" Move it to COLD STORAGE after registering all initial instances.");
723
+ console.log(" Anyone with this key can register new instances under your ForgeID.");
724
+ console.log("");
725
+ console.log(" Next: forge id register [label]");
726
+ console.log("");
727
+ } catch (err) {
728
+ console.error(` Error: ${err.message}`);
729
+ process.exit(1);
730
+ }
731
+ }
732
+
733
+ function cmdIdRegister(args) {
734
+ const label = args.join(" ") || "";
735
+
736
+ try {
737
+ const instance = registerInstance(label);
738
+ console.log("");
739
+ console.log(" ✓ Instance Registered");
740
+ console.log(` ForgeID: ${instance.forge_id.slice(0, 16)}…`);
741
+ console.log(` Instance: #${instance.instance_seq}`);
742
+ console.log(` Label: ${instance.label}`);
743
+ console.log(` Instance ID: ${instance.instance_id.slice(0, 32)}…`);
744
+ console.log(` Registered: ${instance.registered_at}`);
745
+ console.log("");
746
+ console.log(" All TrustAtoms will now be signed with this instance's Ed25519 key.");
747
+ console.log(" The master.key can now be moved to cold storage.");
748
+ console.log("");
749
+ } catch (err) {
750
+ console.error(` Error: ${err.message}`);
751
+ process.exit(1);
752
+ }
753
+ }
754
+
755
+ function cmdIdShow() {
756
+ const genesis = loadGenesis();
757
+ if (!genesis) {
758
+ console.log("\n No ForgeID found. Run 'forge id init' first.\n");
759
+ return;
760
+ }
761
+
762
+ console.log("");
763
+ console.log(" ── ForgeID ──");
764
+ console.log(` ForgeID: ${genesis.forge_id}`);
765
+ console.log(` Master: ${genesis.master_public_key.slice(0, 24)}…`);
766
+ if (genesis.proof_of_existence) console.log(` PoE: ${genesis.proof_of_existence}`);
767
+ console.log(` Created: ${genesis.created_at}`);
768
+
769
+ const instance = loadCurrentInstance();
770
+ if (instance) {
771
+ console.log("");
772
+ console.log(" ── Current Instance ──");
773
+ console.log(` Seq: #${instance.instance_seq}`);
774
+ console.log(` Label: ${instance.label}`);
775
+ console.log(` ID: ${instance.instance_id}`);
776
+ } else {
777
+ console.log("\n No instance registered on this server.");
778
+ console.log(" Run 'forge id register [label]' to register.");
779
+ }
780
+ console.log("");
781
+ }
782
+
783
+ function cmdIdList() {
784
+ const genesis = loadGenesis();
785
+ if (!genesis) {
786
+ console.log("\n No ForgeID found. Run 'forge id init' first.\n");
787
+ return;
788
+ }
789
+
790
+ const instances = loadInstances();
791
+ console.log("");
792
+ console.log(` ForgeID: ${genesis.forge_id.slice(0, 32)}…`);
793
+ console.log(` Instances: ${instances.length}`);
794
+ console.log("");
795
+
796
+ if (instances.length === 0) {
797
+ console.log(" No instances registered.");
798
+ } else {
799
+ const current = loadCurrentInstance();
800
+ for (const inst of instances) {
801
+ const marker = current && current.instance_seq === inst.instance_seq ? " ← this server" : "";
802
+ console.log(` #${inst.instance_seq} ${inst.label.padEnd(20)} ${inst.instance_id.slice(0, 16)}…${marker}`);
803
+ }
804
+ }
805
+ console.log("");
806
+ }
807
+
808
+ function cmdIdVerify() {
809
+ const result = verifyIdentityChain();
810
+ console.log("");
811
+ if (result.valid) {
812
+ console.log(" ✓ Identity chain VALID");
813
+ console.log(` Genesis signature verified`);
814
+ const instances = loadInstances();
815
+ console.log(` ${instances.length} instance registration(s) verified`);
816
+ } else {
817
+ console.log(" ✗ Identity chain BROKEN");
818
+ for (const err of result.errors) {
819
+ console.log(` ✗ ${err}`);
820
+ }
821
+ }
822
+ console.log("");
823
+ }
824
+
825
+ function cmdIdExport() {
826
+ const data = exportIdentity();
827
+ if (!data) {
828
+ console.log("\n No ForgeID found. Run 'forge id init' first.\n");
829
+ return;
830
+ }
831
+ console.log(JSON.stringify(data, null, 2));
832
+ }
833
+
834
+ function cmdId(subArgs) {
835
+ const subCmd = subArgs[0] || "show";
836
+ const rest = subArgs.slice(1);
837
+ switch (subCmd) {
838
+ case "init":
839
+ cmdIdInit(rest);
840
+ break;
841
+ case "register":
842
+ cmdIdRegister(rest);
843
+ break;
844
+ case "show":
845
+ cmdIdShow();
846
+ break;
847
+ case "list":
848
+ cmdIdList();
849
+ break;
850
+ case "verify":
851
+ cmdIdVerify();
852
+ break;
853
+ case "export":
854
+ cmdIdExport();
855
+ break;
856
+ default:
857
+ console.log(` Unknown id command: ${subCmd}`);
858
+ console.log(" Available: init, register, show, list, verify, export");
859
+ break;
860
+ }
861
+ }
862
+
466
863
  /* ================================================================
467
864
  MAIN
468
865
  ================================================================ */
@@ -471,6 +868,14 @@ const args = process.argv.slice(2);
471
868
  const command = args[0] || "help";
472
869
 
473
870
  switch (command) {
871
+ case "init":
872
+ cmdInit(args[1]);
873
+ break;
874
+
875
+ case "identity":
876
+ cmdIdentity(args.slice(1));
877
+ break;
878
+
474
879
  case "scan":
475
880
  cmdScan();
476
881
  break;
@@ -507,17 +912,45 @@ switch (command) {
507
912
  cmdDemo();
508
913
  break;
509
914
 
915
+ case "history":
916
+ cmdHistory(args[1]);
917
+ break;
918
+
919
+ case "mcp":
920
+ // Start MCP server
921
+ import("../mcp/server.js");
922
+ break;
923
+
924
+ case "id":
925
+ cmdId(args.slice(1));
926
+ break;
927
+
510
928
  case "help":
511
929
  default:
512
930
  console.log(`
513
- FORGE — Trust Chain Protocol v0.1
931
+ FORGE — Trust Chain Protocol v0.4
514
932
 
515
933
  Trust = Certainty × Existence
516
934
  Every operation produces a verifiable, undeniable fact.
517
935
 
518
- Commands:
519
- forge scan Enumerate trust assumptions on this system
520
- forge log <action> Record a TrustAtom (one state transition)
936
+ Signing (Ed25519 — simple):
937
+ forge init [name] Generate signing key pair
938
+ forge identity Show your identity
939
+ forge identity --export Export public key for sharing
940
+ forge identity --import <file> Import trusted identity
941
+ forge identity --list List trusted identities
942
+
943
+ Identity (ForgeID — genesis/instance):
944
+ forge id init [--proof "text"] Create ForgeID genesis (master keypair)
945
+ forge id register [label] Register this server as instance
946
+ forge id show Show ForgeID and current instance
947
+ forge id list List all registered instances
948
+ forge id verify Verify identity chain integrity
949
+ forge id export Export public identity (no private keys)
950
+
951
+ Operations:
952
+ forge scan Capture trust baseline (system assumptions)
953
+ forge log <action> Record a TrustAtom (signed if keys/ForgeID exist)
521
954
  forge verify Verify chain integrity
522
955
  forge seal Seal atoms into a Merkle block
523
956
  forge anchor Submit Merkle root to OTS calendars (Level 3)
@@ -525,8 +958,10 @@ switch (command) {
525
958
  forge witness Show witness status for latest block
526
959
  forge witness --bilateral <id> Create bilateral witness receipt
527
960
  forge status Show chain status
528
- forge export Export chain as JSON
961
+ forge history [n] Show recent operations with plaintext (local only)
962
+ forge export Export chain as JSON (no plaintext)
529
963
  forge demo Run full demonstration
964
+ forge mcp Start MCP server (for Claude Code)
530
965
 
531
966
  Witness Hierarchy:
532
967
  Level 1: Self — Only you hold the hash (can be deleted)
@@ -535,13 +970,11 @@ switch (command) {
535
970
  Level 4: Anchored — Bitcoin blockchain (computationally undeletable)
536
971
 
537
972
  Examples:
538
- forge scan
973
+ forge init # simple signing
974
+ forge id init --proof "BBC News 2026" # ForgeID genesis
975
+ forge id register "prod-server"
539
976
  forge log "deployed nginx config"
540
- forge log "opened port 443"
541
- forge verify
542
- forge seal
543
- forge anchor
544
- forge witness --bilateral ops@provider.com
977
+ forge seal && forge anchor
545
978
  `);
546
979
  break;
547
980
  }
package/src/core/chain.js CHANGED
@@ -11,8 +11,9 @@ import { createAtom, verifyAtom, verifyChain, formatAtom } from "./trust-atom.js
11
11
  import { treeFromAtoms, createBlock, getMerkleProof, verifyMerkleProof } from "./merkle.js";
12
12
 
13
13
  export class TrustChain {
14
- constructor(identity) {
14
+ constructor(identity, signFn = null) {
15
15
  this.identity = identity;
16
+ this.signFn = signFn;
16
17
  this.atoms = [];
17
18
  this.blocks = [];
18
19
  }
@@ -34,6 +35,7 @@ export class TrustChain {
34
35
  action,
35
36
  to,
36
37
  prev,
38
+ sign: this.signFn,
37
39
  });
38
40
 
39
41
  this.atoms.push(atom);