soulprint-core 0.1.4 → 0.1.5
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/anti-farming.d.ts +77 -0
- package/dist/anti-farming.js +192 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +10 -1
- package/package.json +1 -1
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Soulprint Anti-Farming Module
|
|
3
|
+
*
|
|
4
|
+
* Detecta y penaliza intentos de farmeo de puntos de reputación.
|
|
5
|
+
*
|
|
6
|
+
* REGLAS INAMOVIBLES (definidas en PROTOCOL):
|
|
7
|
+
* - Un DID solo puede ganar MAX +1 punto por día en total (todos los validadores)
|
|
8
|
+
* - Velocidad máxima: +2 puntos en 7 días vía attestations positivas
|
|
9
|
+
* - Sesiones deben durar al menos MIN_SESSION_SECONDS para ser elegibles a reward
|
|
10
|
+
* - Patrones regulares (robot-like) → detectados como farming → -1
|
|
11
|
+
* - Nuevos DIDs (< PROBATION_DAYS) ganan 0 hasta acumular 2 attestations válidas
|
|
12
|
+
*/
|
|
13
|
+
import { BotAttestation } from "./index.js";
|
|
14
|
+
export declare const FARMING_RULES: Readonly<{
|
|
15
|
+
/** Max puntos que un DID puede ganar en 24h via attestations positivas */
|
|
16
|
+
MAX_GAIN_PER_DAY: 1;
|
|
17
|
+
/** Max puntos acumulados en 7 días */
|
|
18
|
+
MAX_GAIN_PER_WEEK: 2;
|
|
19
|
+
/** Segundos mínimos que debe durar una sesión para ser elegible a reward */
|
|
20
|
+
MIN_SESSION_SECONDS: 30;
|
|
21
|
+
/** Días de probación para DIDs nuevos (cuentan 0 hasta 2 attestations) */
|
|
22
|
+
PROBATION_DAYS: 7;
|
|
23
|
+
/** Max attestations positivas de un MISMO servicio a un DID en 24h */
|
|
24
|
+
MAX_SAME_ISSUER_PER_DAY: 1;
|
|
25
|
+
/** Entropía mínima del patrón de tools para no ser detectado como farming
|
|
26
|
+
* (número mínimo de tools distintos reales para reward) */
|
|
27
|
+
MIN_TOOL_ENTROPY: 4;
|
|
28
|
+
/** Tiempo mínimo entre dos calls a la misma tool (ms) para que no sea robot */
|
|
29
|
+
MIN_TOOL_INTERVAL_MS: 2000;
|
|
30
|
+
/** Si se detecta farming → emitir -1 automático en lugar de +1 */
|
|
31
|
+
FARMING_PENALTY: -1;
|
|
32
|
+
}>;
|
|
33
|
+
export interface SessionEvent {
|
|
34
|
+
tool: string;
|
|
35
|
+
timestamp: number;
|
|
36
|
+
}
|
|
37
|
+
export interface SessionContext {
|
|
38
|
+
did: string;
|
|
39
|
+
startTime: number;
|
|
40
|
+
events: SessionEvent[];
|
|
41
|
+
issuerDid: string;
|
|
42
|
+
}
|
|
43
|
+
export interface FarmingCheckResult {
|
|
44
|
+
isFarming: boolean;
|
|
45
|
+
reason?: string;
|
|
46
|
+
penalty: -1 | 0;
|
|
47
|
+
details?: Record<string, any>;
|
|
48
|
+
}
|
|
49
|
+
export interface DIDAuditEntry {
|
|
50
|
+
dailyGain: number;
|
|
51
|
+
weeklyGain: number;
|
|
52
|
+
dayStart: number;
|
|
53
|
+
weekStart: number;
|
|
54
|
+
firstSeen: number;
|
|
55
|
+
attestCount: number;
|
|
56
|
+
farmingStrikes: number;
|
|
57
|
+
}
|
|
58
|
+
export declare function getOrCreateAudit(did: string): DIDAuditEntry;
|
|
59
|
+
export declare function loadAuditStore(data: Record<string, DIDAuditEntry>): void;
|
|
60
|
+
export declare function exportAuditStore(): Record<string, DIDAuditEntry>;
|
|
61
|
+
/**
|
|
62
|
+
* Analiza una sesión ANTES de emitir la attestation.
|
|
63
|
+
* Si detecta farming, retorna isFarming=true con razón específica.
|
|
64
|
+
*
|
|
65
|
+
* El validator debe llamar esta función ANTES de applyAttestation().
|
|
66
|
+
* Si isFarming=true, emitir -1 en lugar de +1.
|
|
67
|
+
*/
|
|
68
|
+
export declare function checkFarming(session: SessionContext, existingAtts: BotAttestation[]): FarmingCheckResult;
|
|
69
|
+
/**
|
|
70
|
+
* Registra una attestation positiva aprobada en el audit store.
|
|
71
|
+
* Llamar DESPUÉS de confirmar que no es farming y aplicar el +1.
|
|
72
|
+
*/
|
|
73
|
+
export declare function recordApprovedGain(did: string): void;
|
|
74
|
+
/**
|
|
75
|
+
* Registra un strike de farming en el audit store.
|
|
76
|
+
*/
|
|
77
|
+
export declare function recordFarmingStrike(did: string): void;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Soulprint Anti-Farming Module
|
|
4
|
+
*
|
|
5
|
+
* Detecta y penaliza intentos de farmeo de puntos de reputación.
|
|
6
|
+
*
|
|
7
|
+
* REGLAS INAMOVIBLES (definidas en PROTOCOL):
|
|
8
|
+
* - Un DID solo puede ganar MAX +1 punto por día en total (todos los validadores)
|
|
9
|
+
* - Velocidad máxima: +2 puntos en 7 días vía attestations positivas
|
|
10
|
+
* - Sesiones deben durar al menos MIN_SESSION_SECONDS para ser elegibles a reward
|
|
11
|
+
* - Patrones regulares (robot-like) → detectados como farming → -1
|
|
12
|
+
* - Nuevos DIDs (< PROBATION_DAYS) ganan 0 hasta acumular 2 attestations válidas
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.FARMING_RULES = void 0;
|
|
16
|
+
exports.getOrCreateAudit = getOrCreateAudit;
|
|
17
|
+
exports.loadAuditStore = loadAuditStore;
|
|
18
|
+
exports.exportAuditStore = exportAuditStore;
|
|
19
|
+
exports.checkFarming = checkFarming;
|
|
20
|
+
exports.recordApprovedGain = recordApprovedGain;
|
|
21
|
+
exports.recordFarmingStrike = recordFarmingStrike;
|
|
22
|
+
// ── Anti-farming constants (parte del PROTOCOL) ──────────────────────────────
|
|
23
|
+
exports.FARMING_RULES = Object.freeze({
|
|
24
|
+
/** Max puntos que un DID puede ganar en 24h via attestations positivas */
|
|
25
|
+
MAX_GAIN_PER_DAY: 1,
|
|
26
|
+
/** Max puntos acumulados en 7 días */
|
|
27
|
+
MAX_GAIN_PER_WEEK: 2,
|
|
28
|
+
/** Segundos mínimos que debe durar una sesión para ser elegible a reward */
|
|
29
|
+
MIN_SESSION_SECONDS: 30,
|
|
30
|
+
/** Días de probación para DIDs nuevos (cuentan 0 hasta 2 attestations) */
|
|
31
|
+
PROBATION_DAYS: 7,
|
|
32
|
+
/** Max attestations positivas de un MISMO servicio a un DID en 24h */
|
|
33
|
+
MAX_SAME_ISSUER_PER_DAY: 1,
|
|
34
|
+
/** Entropía mínima del patrón de tools para no ser detectado como farming
|
|
35
|
+
* (número mínimo de tools distintos reales para reward) */
|
|
36
|
+
MIN_TOOL_ENTROPY: 4,
|
|
37
|
+
/** Tiempo mínimo entre dos calls a la misma tool (ms) para que no sea robot */
|
|
38
|
+
MIN_TOOL_INTERVAL_MS: 2000,
|
|
39
|
+
/** Si se detecta farming → emitir -1 automático en lugar de +1 */
|
|
40
|
+
FARMING_PENALTY: -1,
|
|
41
|
+
});
|
|
42
|
+
// ── In-memory audit store (el validator lo persiste en disco) ────────────────
|
|
43
|
+
const auditStore = new Map();
|
|
44
|
+
function getOrCreateAudit(did) {
|
|
45
|
+
if (!auditStore.has(did)) {
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
auditStore.set(did, {
|
|
48
|
+
dailyGain: 0,
|
|
49
|
+
weeklyGain: 0,
|
|
50
|
+
dayStart: startOfDay(now),
|
|
51
|
+
weekStart: startOfWeek(now),
|
|
52
|
+
firstSeen: now,
|
|
53
|
+
attestCount: 0,
|
|
54
|
+
farmingStrikes: 0,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return auditStore.get(did);
|
|
58
|
+
}
|
|
59
|
+
function loadAuditStore(data) {
|
|
60
|
+
for (const [did, entry] of Object.entries(data)) {
|
|
61
|
+
auditStore.set(did, entry);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function exportAuditStore() {
|
|
65
|
+
return Object.fromEntries(auditStore.entries());
|
|
66
|
+
}
|
|
67
|
+
// ── Core farming detection ────────────────────────────────────────────────────
|
|
68
|
+
/**
|
|
69
|
+
* Analiza una sesión ANTES de emitir la attestation.
|
|
70
|
+
* Si detecta farming, retorna isFarming=true con razón específica.
|
|
71
|
+
*
|
|
72
|
+
* El validator debe llamar esta función ANTES de applyAttestation().
|
|
73
|
+
* Si isFarming=true, emitir -1 en lugar de +1.
|
|
74
|
+
*/
|
|
75
|
+
function checkFarming(session, existingAtts) {
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
const audit = getOrCreateAudit(session.did);
|
|
78
|
+
resetDailyIfNeeded(audit, now);
|
|
79
|
+
// ── Regla 1: Sesión demasiado corta ──────────────────────────────────────
|
|
80
|
+
const sessionDuration = (now - session.startTime) / 1000;
|
|
81
|
+
if (sessionDuration < exports.FARMING_RULES.MIN_SESSION_SECONDS) {
|
|
82
|
+
return farming(`Session too short: ${sessionDuration.toFixed(1)}s < ${exports.FARMING_RULES.MIN_SESSION_SECONDS}s`, audit);
|
|
83
|
+
}
|
|
84
|
+
// ── Regla 2: Velocidad diaria excedida ───────────────────────────────────
|
|
85
|
+
if (audit.dailyGain >= exports.FARMING_RULES.MAX_GAIN_PER_DAY) {
|
|
86
|
+
return farming(`Daily gain cap reached: +${audit.dailyGain} today (max ${exports.FARMING_RULES.MAX_GAIN_PER_DAY})`, audit);
|
|
87
|
+
}
|
|
88
|
+
// ── Regla 3: Velocidad semanal excedida ──────────────────────────────────
|
|
89
|
+
if (audit.weeklyGain >= exports.FARMING_RULES.MAX_GAIN_PER_WEEK) {
|
|
90
|
+
return farming(`Weekly gain cap reached: +${audit.weeklyGain} this week (max ${exports.FARMING_RULES.MAX_GAIN_PER_WEEK})`, audit);
|
|
91
|
+
}
|
|
92
|
+
// ── Regla 4: Mismo emisor ya attestó hoy ─────────────────────────────────
|
|
93
|
+
const todayStart = startOfDay(now);
|
|
94
|
+
const sameIssuerToday = existingAtts.filter(a => a.issuer_did === session.issuerDid &&
|
|
95
|
+
a.value === 1 &&
|
|
96
|
+
a.timestamp * 1000 >= todayStart).length;
|
|
97
|
+
if (sameIssuerToday >= exports.FARMING_RULES.MAX_SAME_ISSUER_PER_DAY) {
|
|
98
|
+
return farming(`Same issuer already attested today: ${session.issuerDid.slice(0, 20)}... (max ${exports.FARMING_RULES.MAX_SAME_ISSUER_PER_DAY}/day)`, audit);
|
|
99
|
+
}
|
|
100
|
+
// ── Regla 5: Entropía de herramientas insuficiente ───────────────────────
|
|
101
|
+
const uniqueTools = new Set(session.events.map(e => e.tool));
|
|
102
|
+
if (uniqueTools.size < exports.FARMING_RULES.MIN_TOOL_ENTROPY) {
|
|
103
|
+
return farming(`Tool entropy too low: ${uniqueTools.size} distinct tools (min ${exports.FARMING_RULES.MIN_TOOL_ENTROPY})`, audit);
|
|
104
|
+
}
|
|
105
|
+
// ── Regla 6: Patrón robótico (intervalos demasiado regulares) ────────────
|
|
106
|
+
if (isRoboticPattern(session.events)) {
|
|
107
|
+
return farming("Robotic call pattern detected (regular intervals suggest automation)", audit);
|
|
108
|
+
}
|
|
109
|
+
// ── Regla 7: DID en probación con menos de 2 attestations ───────────────
|
|
110
|
+
const daysOld = (now - audit.firstSeen) / (1000 * 60 * 60 * 24);
|
|
111
|
+
if (daysOld < exports.FARMING_RULES.PROBATION_DAYS && audit.attestCount < 2) {
|
|
112
|
+
return farming(`DID in probation: ${daysOld.toFixed(1)} days old, ${audit.attestCount} attestations (need 2+ to earn points in first 7 days)`, audit);
|
|
113
|
+
}
|
|
114
|
+
// ── Sin farming detectado → OK ────────────────────────────────────────────
|
|
115
|
+
return { isFarming: false, penalty: 0 };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Registra una attestation positiva aprobada en el audit store.
|
|
119
|
+
* Llamar DESPUÉS de confirmar que no es farming y aplicar el +1.
|
|
120
|
+
*/
|
|
121
|
+
function recordApprovedGain(did) {
|
|
122
|
+
const audit = getOrCreateAudit(did);
|
|
123
|
+
resetDailyIfNeeded(audit, Date.now());
|
|
124
|
+
audit.dailyGain++;
|
|
125
|
+
audit.weeklyGain++;
|
|
126
|
+
audit.attestCount++;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Registra un strike de farming en el audit store.
|
|
130
|
+
*/
|
|
131
|
+
function recordFarmingStrike(did) {
|
|
132
|
+
const audit = getOrCreateAudit(did);
|
|
133
|
+
audit.farmingStrikes++;
|
|
134
|
+
}
|
|
135
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
136
|
+
function farming(reason, audit) {
|
|
137
|
+
audit.farmingStrikes++;
|
|
138
|
+
return {
|
|
139
|
+
isFarming: true,
|
|
140
|
+
reason,
|
|
141
|
+
penalty: exports.FARMING_RULES.FARMING_PENALTY,
|
|
142
|
+
details: {
|
|
143
|
+
dailyGain: audit.dailyGain,
|
|
144
|
+
weeklyGain: audit.weeklyGain,
|
|
145
|
+
farmingStrikes: audit.farmingStrikes,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Detecta patrones robóticos: intervalos entre calls demasiado regulares.
|
|
151
|
+
* Un humano real varía sus tiempos; un bot suele tener intervals constantes.
|
|
152
|
+
*/
|
|
153
|
+
function isRoboticPattern(events) {
|
|
154
|
+
if (events.length < 4)
|
|
155
|
+
return false;
|
|
156
|
+
const intervals = [];
|
|
157
|
+
for (let i = 1; i < events.length; i++) {
|
|
158
|
+
intervals.push(events[i].timestamp - events[i - 1].timestamp);
|
|
159
|
+
}
|
|
160
|
+
// Si todos los intervalos son casi iguales (stddev < 10% de la media) → robótico
|
|
161
|
+
const mean = intervals.reduce((a, b) => a + b, 0) / intervals.length;
|
|
162
|
+
const stddev = Math.sqrt(intervals.reduce((sum, x) => sum + Math.pow(x - mean, 2), 0) / intervals.length);
|
|
163
|
+
// Adicionalmente: intervalos muy cortos (< MIN_TOOL_INTERVAL_MS) → farming
|
|
164
|
+
const tooFast = intervals.filter(i => i < exports.FARMING_RULES.MIN_TOOL_INTERVAL_MS).length;
|
|
165
|
+
if (tooFast > intervals.length * 0.5)
|
|
166
|
+
return true; // >50% calls muy rápidos
|
|
167
|
+
return mean > 0 && (stddev / mean) < 0.10; // coeficiente de variación < 10%
|
|
168
|
+
}
|
|
169
|
+
function resetDailyIfNeeded(audit, now) {
|
|
170
|
+
const todayStart = startOfDay(now);
|
|
171
|
+
if (audit.dayStart < todayStart) {
|
|
172
|
+
audit.dailyGain = 0;
|
|
173
|
+
audit.dayStart = todayStart;
|
|
174
|
+
}
|
|
175
|
+
const weekStart = startOfWeek(now);
|
|
176
|
+
if (audit.weekStart < weekStart) {
|
|
177
|
+
audit.weeklyGain = 0;
|
|
178
|
+
audit.weekStart = weekStart;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function startOfDay(ms) {
|
|
182
|
+
const d = new Date(ms);
|
|
183
|
+
d.setUTCHours(0, 0, 0, 0);
|
|
184
|
+
return d.getTime();
|
|
185
|
+
}
|
|
186
|
+
function startOfWeek(ms) {
|
|
187
|
+
const d = new Date(ms);
|
|
188
|
+
const day = d.getUTCDay();
|
|
189
|
+
d.setUTCDate(d.getUTCDate() - day);
|
|
190
|
+
d.setUTCHours(0, 0, 0, 0);
|
|
191
|
+
return d.getTime();
|
|
192
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -101,3 +101,5 @@ export declare function calculateScore(credentials: CredentialType[]): number;
|
|
|
101
101
|
export declare function calculateTotalScore(credentials: CredentialType[], botRep?: BotReputation): number;
|
|
102
102
|
export declare function calculateLevel(credentials: CredentialType[]): TrustLevel;
|
|
103
103
|
export { PROTOCOL, isProtocolCompatible, clampMinScore, computeTotalScoreWithFloor, withRetry, } from "./protocol-constants.js";
|
|
104
|
+
export { FARMING_RULES, checkFarming, recordApprovedGain, recordFarmingStrike, getOrCreateAudit, loadAuditStore, exportAuditStore, } from "./anti-farming.js";
|
|
105
|
+
export type { SessionContext, FarmingCheckResult, DIDAuditEntry } from "./anti-farming.js";
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.withRetry = exports.computeTotalScoreWithFloor = exports.clampMinScore = exports.isProtocolCompatible = exports.PROTOCOL = void 0;
|
|
6
|
+
exports.exportAuditStore = exports.loadAuditStore = exports.getOrCreateAudit = exports.recordFarmingStrike = exports.recordApprovedGain = exports.checkFarming = exports.FARMING_RULES = exports.withRetry = exports.computeTotalScoreWithFloor = exports.clampMinScore = exports.isProtocolCompatible = exports.PROTOCOL = void 0;
|
|
7
7
|
exports.generateKeypair = generateKeypair;
|
|
8
8
|
exports.keypairFromPrivateKey = keypairFromPrivateKey;
|
|
9
9
|
exports.deriveNullifier = deriveNullifier;
|
|
@@ -211,3 +211,12 @@ Object.defineProperty(exports, "isProtocolCompatible", { enumerable: true, get:
|
|
|
211
211
|
Object.defineProperty(exports, "clampMinScore", { enumerable: true, get: function () { return protocol_constants_js_1.clampMinScore; } });
|
|
212
212
|
Object.defineProperty(exports, "computeTotalScoreWithFloor", { enumerable: true, get: function () { return protocol_constants_js_1.computeTotalScoreWithFloor; } });
|
|
213
213
|
Object.defineProperty(exports, "withRetry", { enumerable: true, get: function () { return protocol_constants_js_1.withRetry; } });
|
|
214
|
+
// ── Anti-Farming (re-export) ──────────────────────────────────────────────────
|
|
215
|
+
var anti_farming_js_1 = require("./anti-farming.js");
|
|
216
|
+
Object.defineProperty(exports, "FARMING_RULES", { enumerable: true, get: function () { return anti_farming_js_1.FARMING_RULES; } });
|
|
217
|
+
Object.defineProperty(exports, "checkFarming", { enumerable: true, get: function () { return anti_farming_js_1.checkFarming; } });
|
|
218
|
+
Object.defineProperty(exports, "recordApprovedGain", { enumerable: true, get: function () { return anti_farming_js_1.recordApprovedGain; } });
|
|
219
|
+
Object.defineProperty(exports, "recordFarmingStrike", { enumerable: true, get: function () { return anti_farming_js_1.recordFarmingStrike; } });
|
|
220
|
+
Object.defineProperty(exports, "getOrCreateAudit", { enumerable: true, get: function () { return anti_farming_js_1.getOrCreateAudit; } });
|
|
221
|
+
Object.defineProperty(exports, "loadAuditStore", { enumerable: true, get: function () { return anti_farming_js_1.loadAuditStore; } });
|
|
222
|
+
Object.defineProperty(exports, "exportAuditStore", { enumerable: true, get: function () { return anti_farming_js_1.exportAuditStore; } });
|