@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.
- package/dist/index.js +68 -40
- package/dist/index.js.map +1 -1
- package/package.json +18 -4
- package/src/discovery/agent-card-encoder.ts +119 -0
- package/src/discovery/agent-card-schema.ts +87 -0
- package/src/discovery/agent-card-types.ts +99 -0
- package/src/discovery/agent-card.ts +190 -0
- package/src/discovery/bootstrap.ts +63 -0
- package/src/discovery/capability-matcher.ts +167 -0
- package/src/discovery/dht.ts +310 -0
- package/src/discovery/index.ts +3 -0
- package/src/discovery/search-index.ts +247 -0
- package/src/discovery/semantic-search.ts +218 -0
- package/src/identity/did.ts +48 -0
- package/src/identity/document.ts +77 -0
- package/src/identity/index.ts +4 -0
- package/src/identity/keys.ts +79 -0
- package/src/identity/signer.ts +55 -0
- package/src/index.ts +33 -0
- package/src/messaging/codec.ts +47 -0
- package/src/messaging/envelope.ts +107 -0
- package/src/messaging/index.ts +3 -0
- package/src/messaging/router.ts +384 -0
- package/src/transport/connection.ts +77 -0
- package/src/transport/index.ts +2 -0
- package/src/transport/node.ts +152 -0
- package/src/trust/endorsement.ts +167 -0
- package/src/trust/index.ts +194 -0
- package/src/trust/interaction-history.ts +155 -0
- package/src/trust/sybil-defense.ts +232 -0
- package/src/trust/trust-score.ts +136 -0
- package/src/utils/errors.ts +38 -0
- package/src/utils/logger.ts +48 -0
|
@@ -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,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
|
+
}
|