@highway1/core 0.1.39 → 0.1.41

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,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
+ }
@@ -0,0 +1,38 @@
1
+ export class ClawiverseError extends Error {
2
+ constructor(
3
+ message: string,
4
+ public code: string,
5
+ public details?: unknown
6
+ ) {
7
+ super(message);
8
+ this.name = 'ClawiverseError';
9
+ }
10
+ }
11
+
12
+ export class IdentityError extends ClawiverseError {
13
+ constructor(message: string, details?: unknown) {
14
+ super(message, 'IDENTITY_ERROR', details);
15
+ this.name = 'IdentityError';
16
+ }
17
+ }
18
+
19
+ export class TransportError extends ClawiverseError {
20
+ constructor(message: string, details?: unknown) {
21
+ super(message, 'TRANSPORT_ERROR', details);
22
+ this.name = 'TransportError';
23
+ }
24
+ }
25
+
26
+ export class DiscoveryError extends ClawiverseError {
27
+ constructor(message: string, details?: unknown) {
28
+ super(message, 'DISCOVERY_ERROR', details);
29
+ this.name = 'DiscoveryError';
30
+ }
31
+ }
32
+
33
+ export class MessagingError extends ClawiverseError {
34
+ constructor(message: string, details?: unknown) {
35
+ super(message, 'MESSAGING_ERROR', details);
36
+ this.name = 'MessagingError';
37
+ }
38
+ }
@@ -0,0 +1,48 @@
1
+ export enum LogLevel {
2
+ DEBUG = 0,
3
+ INFO = 1,
4
+ WARN = 2,
5
+ ERROR = 3,
6
+ }
7
+
8
+ export class Logger {
9
+ private level: LogLevel;
10
+ private prefix: string;
11
+
12
+ constructor(prefix: string, level: LogLevel = LogLevel.INFO) {
13
+ this.prefix = prefix;
14
+ this.level = level;
15
+ }
16
+
17
+ setLevel(level: LogLevel): void {
18
+ this.level = level;
19
+ }
20
+
21
+ debug(message: string, ...args: unknown[]): void {
22
+ if (this.level <= LogLevel.DEBUG) {
23
+ console.debug(`[${this.prefix}] DEBUG:`, message, ...args);
24
+ }
25
+ }
26
+
27
+ info(message: string, ...args: unknown[]): void {
28
+ if (this.level <= LogLevel.INFO) {
29
+ console.info(`[${this.prefix}] INFO:`, message, ...args);
30
+ }
31
+ }
32
+
33
+ warn(message: string, ...args: unknown[]): void {
34
+ if (this.level <= LogLevel.WARN) {
35
+ console.warn(`[${this.prefix}] WARN:`, message, ...args);
36
+ }
37
+ }
38
+
39
+ error(message: string, ...args: unknown[]): void {
40
+ if (this.level <= LogLevel.ERROR) {
41
+ console.error(`[${this.prefix}] ERROR:`, message, ...args);
42
+ }
43
+ }
44
+ }
45
+
46
+ export const createLogger = (prefix: string, level?: LogLevel): Logger => {
47
+ return new Logger(prefix, level);
48
+ };