@highway1/core 0.1.43 → 0.1.45

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.
@@ -0,0 +1,77 @@
1
+ import type { Libp2p } from 'libp2p';
2
+ import { createLogger } from '../utils/logger.js';
3
+ import { TransportError } from '../utils/errors.js';
4
+
5
+ const logger = createLogger('connection');
6
+
7
+ export interface ConnectionManager {
8
+ connect: (peerId: string) => Promise<void>;
9
+ disconnect: (peerId: string) => Promise<void>;
10
+ isConnected: (peerId: string) => boolean;
11
+ getConnectedPeers: () => string[];
12
+ }
13
+
14
+ /**
15
+ * Create a connection manager for a libp2p node
16
+ */
17
+ export function createConnectionManager(
18
+ libp2p: Libp2p
19
+ ): ConnectionManager {
20
+ return {
21
+ connect: async (peerIdStr: string) => {
22
+ try {
23
+ // Parse peer ID and multiaddr
24
+ const connections = libp2p.getConnections();
25
+ const existing = connections.find(
26
+ (conn) => conn.remotePeer.toString() === peerIdStr
27
+ );
28
+
29
+ if (existing) {
30
+ logger.debug('Already connected to peer', { peerId: peerIdStr });
31
+ return;
32
+ }
33
+
34
+ // In a real implementation, we would dial the peer
35
+ // For now, we rely on libp2p's automatic connection management
36
+ logger.info('Connecting to peer', { peerId: peerIdStr });
37
+ } catch (error) {
38
+ throw new TransportError('Failed to connect to peer', error);
39
+ }
40
+ },
41
+
42
+ disconnect: async (peerIdStr: string) => {
43
+ try {
44
+ const connections = libp2p.getConnections();
45
+ const matching = connections.filter(
46
+ (conn) => conn.remotePeer.toString() === peerIdStr
47
+ );
48
+
49
+ for (const conn of matching) {
50
+ await conn.close();
51
+ }
52
+
53
+ logger.info('Disconnected from peer', { peerId: peerIdStr });
54
+ } catch (error) {
55
+ throw new TransportError('Failed to disconnect from peer', error);
56
+ }
57
+ },
58
+
59
+ isConnected: (peerIdStr: string) => {
60
+ const connections = libp2p.getConnections();
61
+ return connections.some(
62
+ (conn) => conn.remotePeer.toString() === peerIdStr
63
+ );
64
+ },
65
+
66
+ getConnectedPeers: () => {
67
+ const connections = libp2p.getConnections();
68
+ const peers = new Set<string>();
69
+
70
+ for (const conn of connections) {
71
+ peers.add(conn.remotePeer.toString());
72
+ }
73
+
74
+ return Array.from(peers);
75
+ },
76
+ };
77
+ }
@@ -0,0 +1,2 @@
1
+ export * from './node.js';
2
+ export * from './connection.js';
@@ -0,0 +1,152 @@
1
+ import { createLibp2p, Libp2p } from 'libp2p';
2
+ import { tcp } from '@libp2p/tcp';
3
+ import { noise } from '@chainsafe/libp2p-noise';
4
+ import { mplex } from '@libp2p/mplex';
5
+ import { kadDHT, passthroughMapper } from '@libp2p/kad-dht';
6
+ import { bootstrap } from '@libp2p/bootstrap';
7
+ import { identify } from '@libp2p/identify';
8
+ import { ping } from '@libp2p/ping';
9
+ import {
10
+ circuitRelayTransport,
11
+ circuitRelayServer,
12
+ } from '@libp2p/circuit-relay-v2';
13
+ import { createLogger } from '../utils/logger.js';
14
+ import { TransportError } from '../utils/errors.js';
15
+ import type { KeyPair } from '../identity/keys.js';
16
+ import type { PrivateKey } from '@libp2p/interface';
17
+
18
+ const logger = createLogger('transport');
19
+
20
+ export interface TransportConfig {
21
+ keyPair?: KeyPair;
22
+ listenAddresses?: string[];
23
+ bootstrapPeers?: string[];
24
+ enableDHT?: boolean;
25
+ /** Run as a relay server so NAT'd agents can receive messages through this node */
26
+ enableRelay?: boolean;
27
+ /** Reserve a relay slot on bootstrap nodes so others can reach us (only needed for hw1 join) */
28
+ reserveRelaySlot?: boolean;
29
+ /** Enable mDNS local peer discovery (default: false) */
30
+ enableMDNS?: boolean;
31
+ /** libp2p PrivateKey for persistent PeerID (from @libp2p/crypto) */
32
+ privateKey?: PrivateKey;
33
+ }
34
+
35
+ export interface ClawiverseNode {
36
+ libp2p: Libp2p;
37
+ start: () => Promise<void>;
38
+ stop: () => Promise<void>;
39
+ getMultiaddrs: () => string[];
40
+ getPeerId: () => string;
41
+ }
42
+
43
+ /**
44
+ * Create a Clawiverse transport node with libp2p
45
+ */
46
+ export async function createNode(
47
+ config: TransportConfig
48
+ ): Promise<ClawiverseNode> {
49
+ try {
50
+ const {
51
+ listenAddresses = ['/ip4/0.0.0.0/tcp/0'],
52
+ bootstrapPeers = [],
53
+ enableDHT = true,
54
+ enableRelay = false,
55
+ reserveRelaySlot = false,
56
+ enableMDNS = false,
57
+ privateKey,
58
+ } = config;
59
+
60
+ // Reserve relay slots by adding p2p-circuit listen addresses (for hw1 join)
61
+ const relayListenAddrs = reserveRelaySlot
62
+ ? bootstrapPeers.map((peer) => `${peer}/p2p-circuit`)
63
+ : [];
64
+
65
+ const libp2pConfig: any = {
66
+ addresses: {
67
+ listen: [...listenAddresses, ...relayListenAddrs],
68
+ },
69
+ ...(privateKey ? { privateKey } : {}),
70
+ transports: [
71
+ tcp(),
72
+ circuitRelayTransport(),
73
+ ],
74
+ connectionEncrypters: [noise()],
75
+ streamMuxers: [mplex()],
76
+ connectionManager: {
77
+ minConnections: bootstrapPeers.length > 0 ? 1 : 0,
78
+ maxConnections: 50,
79
+ },
80
+ services: {
81
+ identify: identify(),
82
+ ping: ping(),
83
+ },
84
+ };
85
+
86
+ if (enableRelay) {
87
+ libp2pConfig.services.relay = circuitRelayServer({
88
+ reservations: {
89
+ maxReservations: 100, // Allow up to 100 concurrent relay reservations
90
+ },
91
+ });
92
+ }
93
+
94
+ if (enableDHT) {
95
+ libp2pConfig.services.dht = kadDHT({
96
+ clientMode: false,
97
+ peerInfoMapper: passthroughMapper,
98
+ validators: {
99
+ clawiverse: async () => {},
100
+ },
101
+ selectors: {
102
+ clawiverse: () => 0,
103
+ },
104
+ // Optimize for small networks: reduce replication factor and query timeout
105
+ kBucketSize: 20, // Default K=20, keep for compatibility
106
+ querySelfInterval: 30000, // Self-query every 30 seconds (libp2p default)
107
+ // Allow queries to complete faster in small networks
108
+ allowQueryWithZeroPeers: true,
109
+ });
110
+ }
111
+
112
+ const peerDiscovery: any[] = [];
113
+ if (bootstrapPeers.length > 0) peerDiscovery.push(bootstrap({ list: bootstrapPeers }));
114
+ if (enableMDNS) {
115
+ const { mdns } = await import('@libp2p/mdns');
116
+ peerDiscovery.push(mdns());
117
+ }
118
+ if (peerDiscovery.length > 0) libp2pConfig.peerDiscovery = peerDiscovery;
119
+
120
+ const libp2p = await createLibp2p(libp2pConfig);
121
+
122
+ logger.info('Libp2p node created', {
123
+ listenAddresses: libp2pConfig.addresses.listen,
124
+ reserveRelaySlot,
125
+ });
126
+
127
+ return {
128
+ libp2p,
129
+ start: async () => {
130
+ await libp2p.start();
131
+ logger.info('Node started', {
132
+ peerId: libp2p.peerId.toString(),
133
+ addresses: libp2p.getMultiaddrs().map((ma) => ma.toString()),
134
+ relay: enableRelay,
135
+ reserveRelaySlot,
136
+ });
137
+ },
138
+ stop: async () => {
139
+ await libp2p.stop();
140
+ logger.info('Node stopped');
141
+ },
142
+ getMultiaddrs: () => {
143
+ return libp2p.getMultiaddrs().map((ma) => ma.toString());
144
+ },
145
+ getPeerId: () => {
146
+ return libp2p.peerId.toString();
147
+ },
148
+ };
149
+ } catch (error) {
150
+ throw new TransportError('Failed to create transport node', error);
151
+ }
152
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Endorsement System
3
+ *
4
+ * Allows agents to endorse each other, building a web of trust
5
+ */
6
+
7
+ import type { Level } from 'level';
8
+ import { createLogger } from '../utils/logger.js';
9
+
10
+ const logger = createLogger('endorsement');
11
+
12
+ /**
13
+ * Endorsement Record
14
+ */
15
+ export interface Endorsement {
16
+ from: string; // Endorser DID
17
+ to: string; // Endorsed agent DID
18
+ score: number; // 0-1
19
+ reason: string;
20
+ timestamp: number;
21
+ signature: string; // Signed by endorser
22
+ }
23
+
24
+ /**
25
+ * Sign function type
26
+ */
27
+ export type SignFunction = (data: Uint8Array) => Promise<Uint8Array>;
28
+
29
+ /**
30
+ * Verify function type
31
+ */
32
+ export type VerifyFunction = (signature: Uint8Array, data: Uint8Array, publicKey: Uint8Array) => Promise<boolean>;
33
+
34
+ /**
35
+ * Endorsement Manager
36
+ */
37
+ export class EndorsementManager {
38
+ constructor(
39
+ private db: Level<string, Endorsement>,
40
+ private getPublicKey: (did: string) => Promise<Uint8Array>
41
+ ) {}
42
+
43
+ /**
44
+ * Create an endorsement
45
+ */
46
+ async endorse(
47
+ fromDid: string,
48
+ toDid: string,
49
+ score: number,
50
+ reason: string,
51
+ signFn: SignFunction
52
+ ): Promise<Endorsement> {
53
+ if (score < 0 || score > 1) {
54
+ throw new Error('Score must be between 0 and 1');
55
+ }
56
+
57
+ const endorsement: Omit<Endorsement, 'signature'> = {
58
+ from: fromDid,
59
+ to: toDid,
60
+ score,
61
+ reason,
62
+ timestamp: Date.now(),
63
+ };
64
+
65
+ // Sign the endorsement
66
+ const data = new TextEncoder().encode(JSON.stringify(endorsement));
67
+ const signatureBytes = await signFn(data);
68
+ const signature = Buffer.from(signatureBytes).toString('hex');
69
+
70
+ const signedEndorsement: Endorsement = {
71
+ ...endorsement,
72
+ signature,
73
+ };
74
+
75
+ logger.info('Created endorsement', { from: fromDid, to: toDid, score });
76
+ return signedEndorsement;
77
+ }
78
+
79
+ /**
80
+ * Verify endorsement signature
81
+ */
82
+ async verify(endorsement: Endorsement, verifyFn: VerifyFunction): Promise<boolean> {
83
+ try {
84
+ const { signature, ...endorsementWithoutSig } = endorsement;
85
+ const data = new TextEncoder().encode(JSON.stringify(endorsementWithoutSig));
86
+ const signatureBytes = Buffer.from(signature, 'hex');
87
+ const publicKey = await this.getPublicKey(endorsement.from);
88
+
89
+ return await verifyFn(signatureBytes, data, publicKey);
90
+ } catch (error) {
91
+ logger.error('Failed to verify endorsement', { error });
92
+ return false;
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Publish endorsement to local database
98
+ */
99
+ async publish(endorsement: Endorsement): Promise<void> {
100
+ const key = `endorsement:${endorsement.to}:${endorsement.from}`;
101
+ await this.db.put(key, endorsement);
102
+ logger.info('Published endorsement', { from: endorsement.from, to: endorsement.to });
103
+ }
104
+
105
+ /**
106
+ * Get all endorsements for an agent
107
+ */
108
+ async getEndorsements(agentDid: string): Promise<Endorsement[]> {
109
+ const endorsements: Endorsement[] = [];
110
+ const prefix = `endorsement:${agentDid}:`;
111
+
112
+ try {
113
+ for await (const [_, value] of this.db.iterator({
114
+ gte: prefix,
115
+ lte: prefix + '\xff',
116
+ })) {
117
+ endorsements.push(value);
118
+ }
119
+ } catch (error) {
120
+ logger.error('Failed to get endorsements', { agentDid, error });
121
+ }
122
+
123
+ return endorsements;
124
+ }
125
+
126
+ /**
127
+ * Get endorsements given by an agent
128
+ */
129
+ async getEndorsementsBy(fromDid: string): Promise<Endorsement[]> {
130
+ const endorsements: Endorsement[] = [];
131
+
132
+ try {
133
+ for await (const [_, value] of this.db.iterator()) {
134
+ if (value.from === fromDid) {
135
+ endorsements.push(value);
136
+ }
137
+ }
138
+ } catch (error) {
139
+ logger.error('Failed to get endorsements by agent', { fromDid, error });
140
+ }
141
+
142
+ return endorsements;
143
+ }
144
+
145
+ /**
146
+ * Calculate average endorsement score
147
+ */
148
+ async getAverageScore(agentDid: string): Promise<number> {
149
+ const endorsements = await this.getEndorsements(agentDid);
150
+
151
+ if (endorsements.length === 0) {
152
+ return 0;
153
+ }
154
+
155
+ const totalScore = endorsements.reduce((sum, e) => sum + e.score, 0);
156
+ return totalScore / endorsements.length;
157
+ }
158
+
159
+ /**
160
+ * Delete an endorsement
161
+ */
162
+ async deleteEndorsement(fromDid: string, toDid: string): Promise<void> {
163
+ const key = `endorsement:${toDid}:${fromDid}`;
164
+ await this.db.del(key);
165
+ logger.info('Deleted endorsement', { from: fromDid, to: toDid });
166
+ }
167
+ }
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Trust System - Main Export
3
+ *
4
+ * Combines all trust components into a unified interface
5
+ */
6
+
7
+ export * from './trust-score.js';
8
+ export * from './interaction-history.js';
9
+ export * from './endorsement.js';
10
+ export * from './sybil-defense.js';
11
+
12
+ import { Level } from 'level';
13
+ import { TrustMetrics } from './trust-score.js';
14
+ import type { TrustScore, Interaction } from './trust-score.js';
15
+ import { InteractionHistory } from './interaction-history.js';
16
+ import { EndorsementManager } from './endorsement.js';
17
+ import type { Endorsement, SignFunction, VerifyFunction } from './endorsement.js';
18
+ import { SybilDefense } from './sybil-defense.js';
19
+ import { createLogger } from '../utils/logger.js';
20
+
21
+ const logger = createLogger('trust-system');
22
+
23
+ /**
24
+ * Trust System Configuration
25
+ */
26
+ export interface TrustSystemConfig {
27
+ dbPath: string;
28
+ getPublicKey: (did: string) => Promise<Uint8Array>;
29
+ }
30
+
31
+ /**
32
+ * Unified Trust System
33
+ */
34
+ export class TrustSystem {
35
+ private metrics: TrustMetrics;
36
+ private history: InteractionHistory;
37
+ private endorsements: EndorsementManager;
38
+ private sybilDefense: SybilDefense;
39
+ private trustCache = new Map<string, { score: TrustScore; timestamp: number }>();
40
+ private readonly CACHE_TTL = 5 * 60 * 1000; // 5 minutes
41
+
42
+ constructor(config: TrustSystemConfig) {
43
+ this.metrics = new TrustMetrics();
44
+ this.history = new InteractionHistory(`${config.dbPath}/interactions`);
45
+
46
+ const endorsementDb = new Level<string, Endorsement>(`${config.dbPath}/endorsements`, {
47
+ valueEncoding: 'json',
48
+ });
49
+ this.endorsements = new EndorsementManager(endorsementDb, config.getPublicKey);
50
+ this.sybilDefense = new SybilDefense();
51
+ }
52
+
53
+ /**
54
+ * Initialize the trust system
55
+ */
56
+ async start(): Promise<void> {
57
+ await this.history.open();
58
+ logger.info('Trust system started');
59
+ }
60
+
61
+ /**
62
+ * Shutdown the trust system
63
+ */
64
+ async stop(): Promise<void> {
65
+ await this.history.close();
66
+ logger.info('Trust system stopped');
67
+ }
68
+
69
+ /**
70
+ * Record an interaction
71
+ */
72
+ async recordInteraction(interaction: Interaction): Promise<void> {
73
+ await this.history.record(interaction);
74
+ this.sybilDefense.recordRequest(interaction.agentDid);
75
+
76
+ // Invalidate cache
77
+ this.trustCache.delete(interaction.agentDid);
78
+ }
79
+
80
+ /**
81
+ * Get trust score for an agent
82
+ */
83
+ async getTrustScore(agentDid: string): Promise<TrustScore> {
84
+ // Check cache
85
+ const cached = this.trustCache.get(agentDid);
86
+ if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {
87
+ return cached.score;
88
+ }
89
+
90
+ // Calculate fresh score
91
+ const stats = await this.history.getStats(agentDid);
92
+ const endorsementList = await this.endorsements.getEndorsements(agentDid);
93
+ const uptime = 1.0; // TODO: Implement uptime tracking
94
+
95
+ const score = this.metrics.calculateScore(stats, endorsementList.length, uptime);
96
+
97
+ // Cache result
98
+ this.trustCache.set(agentDid, { score, timestamp: Date.now() });
99
+
100
+ return score;
101
+ }
102
+
103
+ /**
104
+ * Create an endorsement
105
+ */
106
+ async endorse(
107
+ fromDid: string,
108
+ toDid: string,
109
+ score: number,
110
+ reason: string,
111
+ signFn: SignFunction
112
+ ): Promise<Endorsement> {
113
+ const endorsement = await this.endorsements.endorse(fromDid, toDid, score, reason, signFn);
114
+ await this.endorsements.publish(endorsement);
115
+
116
+ // Invalidate cache
117
+ this.trustCache.delete(toDid);
118
+
119
+ return endorsement;
120
+ }
121
+
122
+ /**
123
+ * Get endorsements for an agent
124
+ */
125
+ async getEndorsements(agentDid: string): Promise<Endorsement[]> {
126
+ return this.endorsements.getEndorsements(agentDid);
127
+ }
128
+
129
+ /**
130
+ * Verify an endorsement
131
+ */
132
+ async verifyEndorsement(endorsement: Endorsement, verifyFn: VerifyFunction): Promise<boolean> {
133
+ return this.endorsements.verify(endorsement, verifyFn);
134
+ }
135
+
136
+ /**
137
+ * Check if agent should be rate limited
138
+ */
139
+ isRateLimited(agentDid: string): boolean {
140
+ return this.sybilDefense.isRateLimited(agentDid);
141
+ }
142
+
143
+ /**
144
+ * Generate Sybil defense challenge
145
+ */
146
+ generateChallenge(did: string, difficulty?: number) {
147
+ return this.sybilDefense.generateChallenge(did, difficulty);
148
+ }
149
+
150
+ /**
151
+ * Verify Sybil defense challenge
152
+ */
153
+ verifyChallenge(solution: any): boolean {
154
+ return this.sybilDefense.verifyChallenge(solution);
155
+ }
156
+
157
+ /**
158
+ * Get peer trust level
159
+ */
160
+ getPeerTrustLevel(peerId: string) {
161
+ return this.sybilDefense.getPeerTrustLevel(peerId);
162
+ }
163
+
164
+ /**
165
+ * Record peer seen
166
+ */
167
+ recordPeerSeen(peerId: string): void {
168
+ this.sybilDefense.recordPeerSeen(peerId);
169
+ }
170
+
171
+ /**
172
+ * Get interaction history
173
+ */
174
+ async getHistory(agentDid: string, limit?: number): Promise<Interaction[]> {
175
+ return this.history.getHistory(agentDid, limit);
176
+ }
177
+
178
+ /**
179
+ * Clean up old data
180
+ */
181
+ async cleanup(): Promise<void> {
182
+ await this.history.cleanup();
183
+ this.sybilDefense.cleanup();
184
+ this.trustCache.clear();
185
+ logger.info('Trust system cleanup completed');
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Create a trust system instance
191
+ */
192
+ export function createTrustSystem(config: TrustSystemConfig): TrustSystem {
193
+ return new TrustSystem(config);
194
+ }