@highway1/core 0.1.10 → 0.1.13
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 +23 -22
- package/dist/index.js.map +1 -1
- package/package.json +17 -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 +236 -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 +307 -0
- package/src/transport/connection.ts +77 -0
- package/src/transport/index.ts +2 -0
- package/src/transport/node.ts +127 -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,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
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interaction History Tracker
|
|
3
|
+
*
|
|
4
|
+
* Records and queries agent interaction history for trust scoring
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Level } from 'level';
|
|
8
|
+
import type { Interaction, InteractionStats } from './trust-score.js';
|
|
9
|
+
import { createLogger } from '../utils/logger.js';
|
|
10
|
+
|
|
11
|
+
const logger = createLogger('interaction-history');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Interaction History Manager
|
|
15
|
+
*/
|
|
16
|
+
export class InteractionHistory {
|
|
17
|
+
private db: Level<string, Interaction>;
|
|
18
|
+
|
|
19
|
+
constructor(dbPath: string) {
|
|
20
|
+
this.db = new Level(dbPath, { valueEncoding: 'json' });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Open database connection
|
|
25
|
+
*/
|
|
26
|
+
async open(): Promise<void> {
|
|
27
|
+
await this.db.open();
|
|
28
|
+
logger.info('Interaction history database opened', { path: this.db.location });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Close database connection
|
|
33
|
+
*/
|
|
34
|
+
async close(): Promise<void> {
|
|
35
|
+
await this.db.close();
|
|
36
|
+
logger.info('Interaction history database closed');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Record an interaction
|
|
41
|
+
*/
|
|
42
|
+
async record(interaction: Interaction): Promise<void> {
|
|
43
|
+
const key = `interaction:${interaction.agentDid}:${interaction.timestamp}`;
|
|
44
|
+
await this.db.put(key, interaction);
|
|
45
|
+
logger.debug('Recorded interaction', { agentDid: interaction.agentDid, type: interaction.type });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get interaction history for an agent
|
|
50
|
+
*/
|
|
51
|
+
async getHistory(agentDid: string, limit = 100): Promise<Interaction[]> {
|
|
52
|
+
const interactions: Interaction[] = [];
|
|
53
|
+
const prefix = `interaction:${agentDid}:`;
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
for await (const [_, value] of this.db.iterator({
|
|
57
|
+
gte: prefix,
|
|
58
|
+
lte: prefix + '\xff',
|
|
59
|
+
limit,
|
|
60
|
+
reverse: true, // Most recent first
|
|
61
|
+
})) {
|
|
62
|
+
interactions.push(value);
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
logger.error('Failed to get interaction history', { agentDid, error });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return interactions;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get interaction statistics for an agent
|
|
73
|
+
*/
|
|
74
|
+
async getStats(agentDid: string): Promise<InteractionStats> {
|
|
75
|
+
const history = await this.getHistory(agentDid, 1000); // Last 1000 interactions
|
|
76
|
+
|
|
77
|
+
if (history.length === 0) {
|
|
78
|
+
return {
|
|
79
|
+
totalInteractions: 0,
|
|
80
|
+
successRate: 0,
|
|
81
|
+
avgResponseTime: 0,
|
|
82
|
+
lastInteraction: 0,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const successCount = history.filter(i => i.success).length;
|
|
87
|
+
const totalResponseTime = history.reduce((sum, i) => sum + i.responseTime, 0);
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
totalInteractions: history.length,
|
|
91
|
+
successRate: successCount / history.length,
|
|
92
|
+
avgResponseTime: totalResponseTime / history.length,
|
|
93
|
+
lastInteraction: history[0].timestamp,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get all agents with interaction history
|
|
99
|
+
*/
|
|
100
|
+
async getAllAgents(): Promise<string[]> {
|
|
101
|
+
const agents = new Set<string>();
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
for await (const [key] of this.db.iterator()) {
|
|
105
|
+
const match = key.match(/^interaction:([^:]+):/);
|
|
106
|
+
if (match) {
|
|
107
|
+
agents.add(match[1]);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} catch (error) {
|
|
111
|
+
logger.error('Failed to get all agents', { error });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return Array.from(agents);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Delete all interactions for an agent
|
|
119
|
+
*/
|
|
120
|
+
async deleteAgent(agentDid: string): Promise<void> {
|
|
121
|
+
const prefix = `interaction:${agentDid}:`;
|
|
122
|
+
const keysToDelete: string[] = [];
|
|
123
|
+
|
|
124
|
+
for await (const [key] of this.db.iterator({
|
|
125
|
+
gte: prefix,
|
|
126
|
+
lte: prefix + '\xff',
|
|
127
|
+
})) {
|
|
128
|
+
keysToDelete.push(key);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
await this.db.batch(keysToDelete.map(key => ({ type: 'del', key })));
|
|
132
|
+
logger.info('Deleted interaction history', { agentDid, count: keysToDelete.length });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Clean up old interactions (older than 90 days)
|
|
137
|
+
*/
|
|
138
|
+
async cleanup(maxAge = 90 * 24 * 60 * 60 * 1000): Promise<number> {
|
|
139
|
+
const cutoff = Date.now() - maxAge;
|
|
140
|
+
const keysToDelete: string[] = [];
|
|
141
|
+
|
|
142
|
+
for await (const [key, value] of this.db.iterator()) {
|
|
143
|
+
if (value.timestamp < cutoff) {
|
|
144
|
+
keysToDelete.push(key);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (keysToDelete.length > 0) {
|
|
149
|
+
await this.db.batch(keysToDelete.map(key => ({ type: 'del', key })));
|
|
150
|
+
logger.info('Cleaned up old interactions', { count: keysToDelete.length });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return keysToDelete.length;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sybil Defense Mechanisms
|
|
3
|
+
*
|
|
4
|
+
* Protects the network from Sybil attacks through:
|
|
5
|
+
* - Entry cost (Hashcash challenges)
|
|
6
|
+
* - Progressive trust (rate limiting for new agents)
|
|
7
|
+
* - DHT region protection (prefer established peers)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createLogger } from '../utils/logger.js';
|
|
11
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
12
|
+
import { bytesToHex } from '@noble/hashes/utils';
|
|
13
|
+
|
|
14
|
+
const logger = createLogger('sybil-defense');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Hashcash Challenge
|
|
18
|
+
*/
|
|
19
|
+
export interface Challenge {
|
|
20
|
+
did: string;
|
|
21
|
+
difficulty: number; // Number of leading zero bits required
|
|
22
|
+
nonce: string;
|
|
23
|
+
timestamp: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Challenge Solution
|
|
28
|
+
*/
|
|
29
|
+
export interface ChallengeSolution {
|
|
30
|
+
challenge: Challenge;
|
|
31
|
+
solution: string; // Nonce that produces required hash
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Rate Limit Record
|
|
36
|
+
*/
|
|
37
|
+
interface RateLimitRecord {
|
|
38
|
+
did: string;
|
|
39
|
+
requests: number[]; // Timestamps of requests
|
|
40
|
+
firstSeen: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Peer Trust Level
|
|
45
|
+
*/
|
|
46
|
+
export type PeerTrustLevel = 'new' | 'established' | 'trusted';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Sybil Defense Manager
|
|
50
|
+
*/
|
|
51
|
+
export class SybilDefense {
|
|
52
|
+
private rateLimits = new Map<string, RateLimitRecord>();
|
|
53
|
+
private peerFirstSeen = new Map<string, number>();
|
|
54
|
+
private readonly NEW_AGENT_WINDOW = 24 * 60 * 60 * 1000; // 24 hours
|
|
55
|
+
private readonly RATE_LIMIT_WINDOW = 60 * 60 * 1000; // 1 hour
|
|
56
|
+
private readonly MAX_REQUESTS_NEW = 10; // Max requests per hour for new agents
|
|
57
|
+
private readonly ESTABLISHED_THRESHOLD = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Generate a Hashcash challenge for a new DID
|
|
61
|
+
*/
|
|
62
|
+
generateChallenge(did: string, difficulty = 20): Challenge {
|
|
63
|
+
const nonce = this.generateNonce();
|
|
64
|
+
return {
|
|
65
|
+
did,
|
|
66
|
+
difficulty,
|
|
67
|
+
nonce,
|
|
68
|
+
timestamp: Date.now(),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Verify a Hashcash challenge solution
|
|
74
|
+
*/
|
|
75
|
+
verifyChallenge(solution: ChallengeSolution): boolean {
|
|
76
|
+
const { challenge, solution: solutionNonce } = solution;
|
|
77
|
+
|
|
78
|
+
// Check challenge is not too old (valid for 1 hour)
|
|
79
|
+
if (Date.now() - challenge.timestamp > 60 * 60 * 1000) {
|
|
80
|
+
logger.warn('Challenge expired', { did: challenge.did });
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Compute hash
|
|
85
|
+
const data = `${challenge.did}:${challenge.nonce}:${solutionNonce}`;
|
|
86
|
+
const hash = sha256(new TextEncoder().encode(data));
|
|
87
|
+
|
|
88
|
+
// Check leading zeros
|
|
89
|
+
const leadingZeros = this.countLeadingZeroBits(hash);
|
|
90
|
+
const valid = leadingZeros >= challenge.difficulty;
|
|
91
|
+
|
|
92
|
+
if (valid) {
|
|
93
|
+
logger.info('Challenge verified', { did: challenge.did, leadingZeros });
|
|
94
|
+
} else {
|
|
95
|
+
logger.warn('Challenge failed', { did: challenge.did, leadingZeros, required: challenge.difficulty });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return valid;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Check if an agent should be rate limited
|
|
103
|
+
*/
|
|
104
|
+
isRateLimited(did: string): boolean {
|
|
105
|
+
const record = this.rateLimits.get(did);
|
|
106
|
+
if (!record) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const now = Date.now();
|
|
111
|
+
const agentAge = now - record.firstSeen;
|
|
112
|
+
|
|
113
|
+
// Only rate limit new agents
|
|
114
|
+
if (agentAge > this.NEW_AGENT_WINDOW) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Count recent requests
|
|
119
|
+
const recentRequests = record.requests.filter(
|
|
120
|
+
t => now - t < this.RATE_LIMIT_WINDOW
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
return recentRequests.length >= this.MAX_REQUESTS_NEW;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Record a request from an agent
|
|
128
|
+
*/
|
|
129
|
+
recordRequest(did: string): void {
|
|
130
|
+
const now = Date.now();
|
|
131
|
+
let record = this.rateLimits.get(did);
|
|
132
|
+
|
|
133
|
+
if (!record) {
|
|
134
|
+
record = {
|
|
135
|
+
did,
|
|
136
|
+
requests: [],
|
|
137
|
+
firstSeen: now,
|
|
138
|
+
};
|
|
139
|
+
this.rateLimits.set(did, record);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Add request timestamp
|
|
143
|
+
record.requests.push(now);
|
|
144
|
+
|
|
145
|
+
// Clean up old requests
|
|
146
|
+
record.requests = record.requests.filter(
|
|
147
|
+
t => now - t < this.RATE_LIMIT_WINDOW
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
logger.debug('Recorded request', { did, count: record.requests.length });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get peer trust level based on age
|
|
155
|
+
*/
|
|
156
|
+
getPeerTrustLevel(peerId: string): PeerTrustLevel {
|
|
157
|
+
const firstSeen = this.peerFirstSeen.get(peerId);
|
|
158
|
+
if (!firstSeen) {
|
|
159
|
+
return 'new';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const age = Date.now() - firstSeen;
|
|
163
|
+
|
|
164
|
+
if (age < this.NEW_AGENT_WINDOW) {
|
|
165
|
+
return 'new';
|
|
166
|
+
} else if (age < this.ESTABLISHED_THRESHOLD) {
|
|
167
|
+
return 'established';
|
|
168
|
+
} else {
|
|
169
|
+
return 'trusted';
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Record first seen time for a peer
|
|
175
|
+
*/
|
|
176
|
+
recordPeerSeen(peerId: string): void {
|
|
177
|
+
if (!this.peerFirstSeen.has(peerId)) {
|
|
178
|
+
this.peerFirstSeen.set(peerId, Date.now());
|
|
179
|
+
logger.debug('Recorded new peer', { peerId });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Clean up old records
|
|
185
|
+
*/
|
|
186
|
+
cleanup(): void {
|
|
187
|
+
const now = Date.now();
|
|
188
|
+
const cutoff = now - this.NEW_AGENT_WINDOW;
|
|
189
|
+
|
|
190
|
+
// Clean up rate limits for old agents
|
|
191
|
+
for (const [did, record] of this.rateLimits.entries()) {
|
|
192
|
+
if (record.firstSeen < cutoff) {
|
|
193
|
+
this.rateLimits.delete(did);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
logger.info('Cleaned up Sybil defense records', {
|
|
198
|
+
rateLimits: this.rateLimits.size,
|
|
199
|
+
peers: this.peerFirstSeen.size,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Generate a random nonce
|
|
205
|
+
*/
|
|
206
|
+
private generateNonce(): string {
|
|
207
|
+
const bytes = new Uint8Array(16);
|
|
208
|
+
crypto.getRandomValues(bytes);
|
|
209
|
+
return bytesToHex(bytes);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Count leading zero bits in a hash
|
|
214
|
+
*/
|
|
215
|
+
private countLeadingZeroBits(hash: Uint8Array): number {
|
|
216
|
+
let count = 0;
|
|
217
|
+
for (const byte of hash) {
|
|
218
|
+
if (byte === 0) {
|
|
219
|
+
count += 8;
|
|
220
|
+
} else {
|
|
221
|
+
// Count leading zeros in this byte
|
|
222
|
+
let b = byte;
|
|
223
|
+
while ((b & 0x80) === 0) {
|
|
224
|
+
count++;
|
|
225
|
+
b <<= 1;
|
|
226
|
+
}
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return count;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trust Score System
|
|
3
|
+
*
|
|
4
|
+
* Tracks agent reputation based on interactions, endorsements,
|
|
5
|
+
* and network behavior.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Trust Score Metrics
|
|
10
|
+
*/
|
|
11
|
+
export interface TrustScore {
|
|
12
|
+
interactionScore: number; // 0-1, based on successful interactions
|
|
13
|
+
endorsements: number; // Count of endorsements
|
|
14
|
+
completionRate: number; // % of completed tasks (0-1)
|
|
15
|
+
responseTime: number; // Average response time (ms)
|
|
16
|
+
uptime: number; // % of time online last 30 days (0-1)
|
|
17
|
+
lastUpdated: number; // Timestamp
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Interaction Record
|
|
22
|
+
*/
|
|
23
|
+
export interface Interaction {
|
|
24
|
+
agentDid: string;
|
|
25
|
+
timestamp: number;
|
|
26
|
+
type: 'message' | 'task' | 'query';
|
|
27
|
+
success: boolean;
|
|
28
|
+
responseTime: number;
|
|
29
|
+
rating?: number; // 1-5 stars (optional user rating)
|
|
30
|
+
feedback?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Interaction Statistics
|
|
35
|
+
*/
|
|
36
|
+
export interface InteractionStats {
|
|
37
|
+
totalInteractions: number;
|
|
38
|
+
successRate: number;
|
|
39
|
+
avgResponseTime: number;
|
|
40
|
+
lastInteraction: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Trust Metrics Calculator
|
|
45
|
+
*/
|
|
46
|
+
export class TrustMetrics {
|
|
47
|
+
/**
|
|
48
|
+
* Calculate trust score from interaction history
|
|
49
|
+
*/
|
|
50
|
+
calculateScore(stats: InteractionStats, endorsements: number, uptime: number): TrustScore {
|
|
51
|
+
// Interaction score: weighted by success rate and volume
|
|
52
|
+
const volumeWeight = Math.min(stats.totalInteractions / 100, 1); // Cap at 100 interactions
|
|
53
|
+
const interactionScore = stats.successRate * volumeWeight;
|
|
54
|
+
|
|
55
|
+
// Completion rate is same as success rate for now
|
|
56
|
+
const completionRate = stats.successRate;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
interactionScore,
|
|
60
|
+
endorsements,
|
|
61
|
+
completionRate,
|
|
62
|
+
responseTime: stats.avgResponseTime,
|
|
63
|
+
uptime,
|
|
64
|
+
lastUpdated: Date.now(),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Calculate overall trust level (0-1)
|
|
70
|
+
*/
|
|
71
|
+
calculateOverallTrust(score: TrustScore): number {
|
|
72
|
+
// Weighted average of different factors
|
|
73
|
+
const weights = {
|
|
74
|
+
interaction: 0.4,
|
|
75
|
+
endorsement: 0.2,
|
|
76
|
+
completion: 0.2,
|
|
77
|
+
uptime: 0.2,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const endorsementScore = Math.min(score.endorsements / 10, 1); // Cap at 10 endorsements
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
score.interactionScore * weights.interaction +
|
|
84
|
+
endorsementScore * weights.endorsement +
|
|
85
|
+
score.completionRate * weights.completion +
|
|
86
|
+
score.uptime * weights.uptime
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get trust level category
|
|
92
|
+
*/
|
|
93
|
+
getTrustLevel(score: TrustScore): 'new' | 'low' | 'medium' | 'high' | 'trusted' {
|
|
94
|
+
const overall = this.calculateOverallTrust(score);
|
|
95
|
+
|
|
96
|
+
if (score.interactionScore === 0) return 'new';
|
|
97
|
+
if (overall < 0.3) return 'low';
|
|
98
|
+
if (overall < 0.6) return 'medium';
|
|
99
|
+
if (overall < 0.8) return 'high';
|
|
100
|
+
return 'trusted';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Check if agent should be rate limited
|
|
105
|
+
*/
|
|
106
|
+
shouldRateLimit(score: TrustScore, agentAge: number): boolean {
|
|
107
|
+
const overall = this.calculateOverallTrust(score);
|
|
108
|
+
const ONE_DAY = 24 * 60 * 60 * 1000;
|
|
109
|
+
|
|
110
|
+
// New agents (< 24 hours) with low trust
|
|
111
|
+
if (agentAge < ONE_DAY && overall < 0.3) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Very low trust regardless of age
|
|
116
|
+
if (overall < 0.1) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Default trust score for new agents
|
|
126
|
+
*/
|
|
127
|
+
export function createDefaultTrustScore(): TrustScore {
|
|
128
|
+
return {
|
|
129
|
+
interactionScore: 0,
|
|
130
|
+
endorsements: 0,
|
|
131
|
+
completionRate: 0,
|
|
132
|
+
responseTime: 0,
|
|
133
|
+
uptime: 1.0, // Assume online initially
|
|
134
|
+
lastUpdated: Date.now(),
|
|
135
|
+
};
|
|
136
|
+
}
|