soulprint-core 0.1.4 → 0.1.6
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/dist/protocol-constants.d.ts +27 -0
- package/dist/protocol-constants.js +28 -0
- 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; } });
|
|
@@ -60,6 +60,33 @@ export declare const PROTOCOL: Readonly<{
|
|
|
60
60
|
readonly RATE_LIMIT_MAX: 100;
|
|
61
61
|
/** Ventana de tiempo del rate limiter (ms). */
|
|
62
62
|
readonly RATE_LIMIT_WINDOW_MS: 60000;
|
|
63
|
+
/**
|
|
64
|
+
* Similitud mínima para verificación DOCUMENTO vs SELFIE.
|
|
65
|
+
* Fotos de documentos son más antiguas/pequeñas que selfies en vivo.
|
|
66
|
+
* Validado con cédula CO real: similitud 0.365 → VERIFICADO.
|
|
67
|
+
* Una persona diferente obtiene < 0.15 con el mismo modelo.
|
|
68
|
+
*
|
|
69
|
+
* NO MODIFICAR en runtime. Cambiar requiere nuevo SIP + bump de versión.
|
|
70
|
+
*/
|
|
71
|
+
readonly FACE_SIM_DOC_SELFIE: 0.35;
|
|
72
|
+
/**
|
|
73
|
+
* Similitud mínima para verificación SELFIE vs SELFIE (re-verificación,
|
|
74
|
+
* liveness check, o comparación entre sesiones del mismo usuario).
|
|
75
|
+
* Umbral más estricto porque ambas fotos son recientes y de alta calidad.
|
|
76
|
+
*/
|
|
77
|
+
readonly FACE_SIM_SELFIE_SELFIE: 0.65;
|
|
78
|
+
/**
|
|
79
|
+
* Número de dimensiones del embedding usadas para derivar el face_key.
|
|
80
|
+
* Las primeras 32 dims capturan suficiente identidad y son más robustas
|
|
81
|
+
* ante variaciones de iluminación/ángulo.
|
|
82
|
+
*/
|
|
83
|
+
readonly FACE_KEY_DIMS: 32;
|
|
84
|
+
/**
|
|
85
|
+
* Decimales de precisión al redondear las dimensiones del embedding.
|
|
86
|
+
* 1 decimal absorbe el ruido natural de InsightFace (±0.01).
|
|
87
|
+
* Garantiza que la misma cara → mismo face_key aunque la foto varíe levemente.
|
|
88
|
+
*/
|
|
89
|
+
readonly FACE_KEY_PRECISION: 1;
|
|
63
90
|
}>;
|
|
64
91
|
export type ProtocolConstants = typeof PROTOCOL;
|
|
65
92
|
/**
|
|
@@ -74,6 +74,34 @@ exports.PROTOCOL = Object.freeze({
|
|
|
74
74
|
RATE_LIMIT_MAX: 100,
|
|
75
75
|
/** Ventana de tiempo del rate limiter (ms). */
|
|
76
76
|
RATE_LIMIT_WINDOW_MS: 60_000,
|
|
77
|
+
// ── Biometric thresholds (INAMOVIBLES) ─────────────────────────────────────
|
|
78
|
+
/**
|
|
79
|
+
* Similitud mínima para verificación DOCUMENTO vs SELFIE.
|
|
80
|
+
* Fotos de documentos son más antiguas/pequeñas que selfies en vivo.
|
|
81
|
+
* Validado con cédula CO real: similitud 0.365 → VERIFICADO.
|
|
82
|
+
* Una persona diferente obtiene < 0.15 con el mismo modelo.
|
|
83
|
+
*
|
|
84
|
+
* NO MODIFICAR en runtime. Cambiar requiere nuevo SIP + bump de versión.
|
|
85
|
+
*/
|
|
86
|
+
FACE_SIM_DOC_SELFIE: 0.35,
|
|
87
|
+
/**
|
|
88
|
+
* Similitud mínima para verificación SELFIE vs SELFIE (re-verificación,
|
|
89
|
+
* liveness check, o comparación entre sesiones del mismo usuario).
|
|
90
|
+
* Umbral más estricto porque ambas fotos son recientes y de alta calidad.
|
|
91
|
+
*/
|
|
92
|
+
FACE_SIM_SELFIE_SELFIE: 0.65,
|
|
93
|
+
/**
|
|
94
|
+
* Número de dimensiones del embedding usadas para derivar el face_key.
|
|
95
|
+
* Las primeras 32 dims capturan suficiente identidad y son más robustas
|
|
96
|
+
* ante variaciones de iluminación/ángulo.
|
|
97
|
+
*/
|
|
98
|
+
FACE_KEY_DIMS: 32,
|
|
99
|
+
/**
|
|
100
|
+
* Decimales de precisión al redondear las dimensiones del embedding.
|
|
101
|
+
* 1 decimal absorbe el ruido natural de InsightFace (±0.01).
|
|
102
|
+
* Garantiza que la misma cara → mismo face_key aunque la foto varíe levemente.
|
|
103
|
+
*/
|
|
104
|
+
FACE_KEY_PRECISION: 1,
|
|
77
105
|
});
|
|
78
106
|
/**
|
|
79
107
|
* Verifica que este nodo es compatible con la versión de protocolo recibida.
|