@moltlaunch/sdk 2.1.0 → 2.3.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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +427 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moltlaunch/sdk",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "MoltLaunch SDK - On-chain AI verification for AI agents",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
package/src/index.js CHANGED
@@ -484,6 +484,433 @@ class MoltLaunch {
484
484
  };
485
485
  return costs[proofType] || costs.threshold;
486
486
  }
487
+
488
+ // ==========================================
489
+ // HARDWARE-ANCHORED IDENTITY (Anti-Sybil)
490
+ // DePIN-Rooted Device Identity
491
+ // ==========================================
492
+
493
+ /**
494
+ * Collect environment fingerprint for hardware-anchored identity
495
+ * @returns {object} Raw fingerprint data
496
+ * @private
497
+ */
498
+ _collectFingerprint() {
499
+ const crypto = require('crypto');
500
+ const os = require('os');
501
+
502
+ const hardware = {
503
+ platform: os.platform(),
504
+ arch: os.arch(),
505
+ cpus: os.cpus().length,
506
+ cpuModel: os.cpus()[0]?.model || 'unknown',
507
+ totalMemory: os.totalmem(),
508
+ hostname: os.hostname(),
509
+ };
510
+
511
+ const runtime = {
512
+ nodeVersion: process.version,
513
+ pid: process.pid,
514
+ execPath: process.execPath,
515
+ cwd: process.cwd(),
516
+ env: {
517
+ USER: process.env.USER || process.env.USERNAME || 'unknown',
518
+ HOME: process.env.HOME || process.env.USERPROFILE || 'unknown',
519
+ SHELL: process.env.SHELL || 'unknown',
520
+ }
521
+ };
522
+
523
+ // Try to get network interfaces for fingerprinting
524
+ const nets = os.networkInterfaces();
525
+ const networkFingerprint = Object.keys(nets).sort().map(name => {
526
+ const iface = nets[name].find(n => !n.internal && n.family === 'IPv4');
527
+ return iface ? `${name}:${iface.mac}` : null;
528
+ }).filter(Boolean).join('|');
529
+
530
+ return { hardware, runtime, networkFingerprint };
531
+ }
532
+
533
+ /**
534
+ * Try to read TPM endorsement key hash for hardware-rooted identity
535
+ * @returns {string|null} SHA-256 hash of TPM data, or null if unavailable
536
+ * @private
537
+ */
538
+ _getTPMFingerprint() {
539
+ const crypto = require('crypto');
540
+ const fs = require('fs');
541
+ const os = require('os');
542
+
543
+ // TPM 2.0 paths (Linux)
544
+ const tpmPaths = [
545
+ '/sys/class/tpm/tpm0/device/description',
546
+ '/sys/class/tpm/tpm0/tpm_version_major',
547
+ '/sys/class/dmi/id/board_serial',
548
+ '/sys/class/dmi/id/product_uuid',
549
+ '/sys/class/dmi/id/chassis_serial',
550
+ ];
551
+
552
+ const tpmData = [];
553
+ for (const p of tpmPaths) {
554
+ try {
555
+ const data = fs.readFileSync(p, 'utf-8').trim();
556
+ if (data && data !== 'None' && data !== '') {
557
+ tpmData.push(data);
558
+ }
559
+ } catch {}
560
+ }
561
+
562
+ // macOS: use IOPlatformUUID
563
+ if (os.platform() === 'darwin') {
564
+ try {
565
+ const { execSync } = require('child_process');
566
+ const uuid = execSync('ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID', { encoding: 'utf-8' });
567
+ const match = uuid.match(/"([A-F0-9-]+)"/);
568
+ if (match) tpmData.push(match[1]);
569
+ } catch {}
570
+ }
571
+
572
+ if (tpmData.length === 0) return null;
573
+
574
+ return crypto.createHash('sha256')
575
+ .update(tpmData.join('|'))
576
+ .digest('hex');
577
+ }
578
+
579
+ /**
580
+ * Register a DePIN device attestation for hardware-rooted identity
581
+ * Links agent identity to a physically verified DePIN device
582
+ *
583
+ * @param {object} options - DePIN registration options
584
+ * @param {string} options.provider - DePIN provider name (e.g., 'io.net', 'akash', 'render')
585
+ * @param {string} options.deviceId - Device ID from the DePIN provider
586
+ * @param {string} [options.attestation] - Optional attestation data from the provider
587
+ * @param {string} options.agentId - Agent ID to bind DePIN identity to
588
+ * @returns {Promise<DePINRegistrationResult>}
589
+ */
590
+ async registerDePINDevice(options = {}) {
591
+ const { provider, deviceId, attestation, agentId } = options;
592
+
593
+ const supported = ['io.net', 'akash', 'render', 'helium', 'hivemapper', 'nosana'];
594
+
595
+ if (!supported.includes(provider)) {
596
+ throw new Error(`Unsupported DePIN provider. Supported: ${supported.join(', ')}`);
597
+ }
598
+
599
+ const res = await fetch(`${this.baseUrl}/api/identity/depin`, {
600
+ method: 'POST',
601
+ headers: { 'Content-Type': 'application/json' },
602
+ body: JSON.stringify({
603
+ agentId,
604
+ depinProvider: provider,
605
+ deviceId,
606
+ attestation,
607
+ timestamp: Date.now()
608
+ })
609
+ });
610
+
611
+ if (!res.ok) throw new Error(`DePIN registration failed: ${res.status}`);
612
+ return res.json();
613
+ }
614
+
615
+ /**
616
+ * Get identity trust report for an agent
617
+ * Shows trust ladder breakdown including DePIN and TPM attestation levels
618
+ *
619
+ * @param {string} agentId - Agent ID to get report for
620
+ * @returns {Promise<IdentityReport>}
621
+ */
622
+ async getIdentityReport(agentId) {
623
+ const res = await fetch(`${this.baseUrl}/api/identity/${encodeURIComponent(agentId)}/report`);
624
+ if (!res.ok) throw new Error(`API error: ${res.status}`);
625
+ return res.json();
626
+ }
627
+
628
+ /**
629
+ * Generate a hardware-anchored identity hash
630
+ * Combines hardware, runtime, code, and network fingerprints into a deterministic identity
631
+ *
632
+ * @param {object} options - Identity options
633
+ * @param {boolean} [options.includeHardware=true] - Include hardware fingerprint (CPU, memory)
634
+ * @param {boolean} [options.includeRuntime=true] - Include runtime fingerprint (Node version, OS)
635
+ * @param {boolean} [options.includeCode=false] - Include code hash (hash of main module)
636
+ * @param {string} [options.codeEntry] - Path to agent's main module for code hashing
637
+ * @param {string} [options.agentId] - Agent ID to bind identity to
638
+ * @param {boolean} [options.anchor=false] - Anchor identity on Solana
639
+ * @returns {Promise<IdentityResult>}
640
+ */
641
+ async generateIdentity(options = {}) {
642
+ const crypto = require('crypto');
643
+ const {
644
+ includeHardware = true,
645
+ includeRuntime = true,
646
+ includeCode = false,
647
+ includeTPM = false,
648
+ depinProvider,
649
+ depinDeviceId,
650
+ codeEntry,
651
+ agentId,
652
+ anchor = false
653
+ } = options;
654
+
655
+ const fingerprint = this._collectFingerprint();
656
+ const components = [];
657
+
658
+ if (includeHardware) {
659
+ const hwHash = crypto.createHash('sha256')
660
+ .update(JSON.stringify(fingerprint.hardware))
661
+ .digest('hex');
662
+ components.push(`hw:${hwHash}`);
663
+ }
664
+
665
+ if (includeRuntime) {
666
+ const rtHash = crypto.createHash('sha256')
667
+ .update(JSON.stringify(fingerprint.runtime))
668
+ .digest('hex');
669
+ components.push(`rt:${rtHash}`);
670
+ }
671
+
672
+ if (includeCode && codeEntry) {
673
+ try {
674
+ const fs = require('fs');
675
+ const codeContent = fs.readFileSync(codeEntry, 'utf-8');
676
+ const codeHash = crypto.createHash('sha256')
677
+ .update(codeContent)
678
+ .digest('hex');
679
+ components.push(`code:${codeHash}`);
680
+ } catch (e) {
681
+ components.push(`code:unavailable`);
682
+ }
683
+ }
684
+
685
+ if (fingerprint.networkFingerprint) {
686
+ const netHash = crypto.createHash('sha256')
687
+ .update(fingerprint.networkFingerprint)
688
+ .digest('hex');
689
+ components.push(`net:${netHash}`);
690
+ }
691
+
692
+ // TPM attestation (hardware-rooted identity - trust level 4)
693
+ let tpmHash = null;
694
+ if (includeTPM) {
695
+ tpmHash = this._getTPMFingerprint();
696
+ if (tpmHash) {
697
+ components.push(`tpm:${tpmHash}`);
698
+ }
699
+ }
700
+
701
+ // DePIN device attestation (highest trust level 5)
702
+ if (depinProvider && depinDeviceId) {
703
+ const depinHash = crypto.createHash('sha256')
704
+ .update(`depin:${depinProvider}:${depinDeviceId}`)
705
+ .digest('hex');
706
+ components.push(`depin:${depinHash}`);
707
+ }
708
+
709
+ // Generate deterministic identity hash
710
+ const identityHash = crypto.createHash('sha256')
711
+ .update(components.join('|'))
712
+ .digest('hex');
713
+
714
+ const identity = {
715
+ hash: identityHash,
716
+ components: components.length,
717
+ includesHardware: includeHardware,
718
+ includesRuntime: includeRuntime,
719
+ includesCode: includeCode && !!codeEntry,
720
+ includesNetwork: !!fingerprint.networkFingerprint,
721
+ includesTPM: includeTPM && !!tpmHash,
722
+ tpmHash: tpmHash || null,
723
+ depinProvider: depinProvider || null,
724
+ depinDeviceId: depinDeviceId || null,
725
+ generatedAt: new Date().toISOString(),
726
+ expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // 30 days
727
+ agentId: agentId || null
728
+ };
729
+
730
+ // Register with MoltLaunch API
731
+ if (agentId) {
732
+ try {
733
+ const res = await fetch(`${this.baseUrl}/api/identity/register`, {
734
+ method: 'POST',
735
+ headers: {
736
+ 'Content-Type': 'application/json',
737
+ ...(this.apiKey && { 'Authorization': `Bearer ${this.apiKey}` })
738
+ },
739
+ body: JSON.stringify({
740
+ agentId,
741
+ identityHash,
742
+ components: components.length,
743
+ includesHardware: includeHardware,
744
+ includesCode: includeCode
745
+ })
746
+ });
747
+
748
+ if (res.ok) {
749
+ const registration = await res.json();
750
+ identity.registered = true;
751
+ identity.registrationId = registration.registrationId;
752
+ } else {
753
+ identity.registered = false;
754
+ identity.registrationError = `API ${res.status}`;
755
+ }
756
+ } catch (e) {
757
+ identity.registered = false;
758
+ identity.registrationError = e.message;
759
+ }
760
+ }
761
+
762
+ // Anchor on Solana if requested
763
+ if (anchor && agentId) {
764
+ try {
765
+ const res = await fetch(`${this.baseUrl}/api/anchor/verification`, {
766
+ method: 'POST',
767
+ headers: { 'Content-Type': 'application/json' },
768
+ body: JSON.stringify({
769
+ agentId,
770
+ attestationHash: identityHash
771
+ })
772
+ });
773
+
774
+ if (res.ok) {
775
+ const anchorResult = await res.json();
776
+ identity.anchored = true;
777
+ identity.anchorSignature = anchorResult.signature;
778
+ identity.anchorExplorer = anchorResult.explorer;
779
+ } else {
780
+ identity.anchored = false;
781
+ }
782
+ } catch (e) {
783
+ identity.anchored = false;
784
+ identity.anchorError = e.message;
785
+ }
786
+ }
787
+
788
+ return identity;
789
+ }
790
+
791
+ /**
792
+ * Verify an agent's identity against their registered fingerprint
793
+ * @param {string} agentId - Agent ID to verify
794
+ * @returns {Promise<IdentityVerification>}
795
+ */
796
+ async verifyIdentity(agentId) {
797
+ // Generate current fingerprint
798
+ const currentIdentity = await this.generateIdentity({ agentId });
799
+
800
+ // Check against registered identity
801
+ try {
802
+ const res = await fetch(`${this.baseUrl}/api/identity/${encodeURIComponent(agentId)}`);
803
+ if (!res.ok) {
804
+ return {
805
+ valid: false,
806
+ agentId,
807
+ reason: 'No registered identity found',
808
+ currentHash: currentIdentity.hash
809
+ };
810
+ }
811
+
812
+ const registered = await res.json();
813
+ const matches = registered.identityHash === currentIdentity.hash;
814
+
815
+ return {
816
+ valid: matches,
817
+ agentId,
818
+ currentHash: currentIdentity.hash,
819
+ registeredHash: registered.identityHash,
820
+ match: matches,
821
+ registeredAt: registered.registeredAt,
822
+ reason: matches ? 'Identity confirmed' : 'Identity mismatch — different hardware or code'
823
+ };
824
+ } catch (e) {
825
+ return {
826
+ valid: false,
827
+ agentId,
828
+ reason: e.message,
829
+ currentHash: currentIdentity.hash
830
+ };
831
+ }
832
+ }
833
+
834
+ /**
835
+ * Check if two agents have the same hardware fingerprint (Sybil detection)
836
+ * @param {string} agentId1 - First agent
837
+ * @param {string} agentId2 - Second agent
838
+ * @returns {Promise<SybilCheck>}
839
+ */
840
+ async checkSybil(agentId1, agentId2) {
841
+ try {
842
+ const [id1, id2] = await Promise.all([
843
+ fetch(`${this.baseUrl}/api/identity/${encodeURIComponent(agentId1)}`).then(r => r.json()),
844
+ fetch(`${this.baseUrl}/api/identity/${encodeURIComponent(agentId2)}`).then(r => r.json())
845
+ ]);
846
+
847
+ const sameIdentity = id1.identityHash === id2.identityHash;
848
+
849
+ return {
850
+ agentId1,
851
+ agentId2,
852
+ sameIdentity,
853
+ sybilRisk: sameIdentity ? 'HIGH' : 'LOW',
854
+ reason: sameIdentity
855
+ ? 'Same hardware fingerprint — likely same operator'
856
+ : 'Different hardware fingerprints — likely different operators',
857
+ recommendation: sameIdentity
858
+ ? 'Do not seat at same table'
859
+ : 'Safe to interact'
860
+ };
861
+ } catch (e) {
862
+ return {
863
+ agentId1,
864
+ agentId2,
865
+ sameIdentity: null,
866
+ sybilRisk: 'UNKNOWN',
867
+ reason: `Could not compare: ${e.message}`
868
+ };
869
+ }
870
+ }
871
+
872
+ /**
873
+ * Check a list of agents for Sybil clusters (table seating check)
874
+ * @param {string[]} agentIds - List of agent IDs to check
875
+ * @returns {Promise<SybilTableCheck>}
876
+ */
877
+ async checkTableSybils(agentIds) {
878
+ // Fetch all identities
879
+ const identities = {};
880
+ for (const id of agentIds) {
881
+ try {
882
+ const res = await fetch(`${this.baseUrl}/api/identity/${encodeURIComponent(id)}`);
883
+ if (res.ok) {
884
+ const data = await res.json();
885
+ identities[id] = data.identityHash;
886
+ }
887
+ } catch {
888
+ // Skip agents without identity
889
+ }
890
+ }
891
+
892
+ // Find clusters (same identity hash)
893
+ const hashToAgents = {};
894
+ for (const [agentId, hash] of Object.entries(identities)) {
895
+ if (!hashToAgents[hash]) hashToAgents[hash] = [];
896
+ hashToAgents[hash].push(agentId);
897
+ }
898
+
899
+ const clusters = Object.values(hashToAgents).filter(group => group.length > 1);
900
+ const flagged = clusters.flat();
901
+
902
+ return {
903
+ totalAgents: agentIds.length,
904
+ identifiedAgents: Object.keys(identities).length,
905
+ unidentifiedAgents: agentIds.filter(id => !identities[id]),
906
+ sybilClusters: clusters,
907
+ flaggedAgents: flagged,
908
+ safe: clusters.length === 0,
909
+ recommendation: clusters.length === 0
910
+ ? 'No Sybil clusters detected — safe to proceed'
911
+ : `${clusters.length} Sybil cluster(s) detected — ${flagged.length} agents share hardware`
912
+ };
913
+ }
487
914
  }
488
915
 
489
916
  // Scoring helpers