soulprint-network 0.2.2 → 0.2.3

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.
Files changed (2) hide show
  1. package/dist/validator.js +58 -7
  2. package/package.json +3 -3
package/dist/validator.js CHANGED
@@ -2,7 +2,7 @@ import { createServer } from "node:http";
2
2
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { homedir } from "node:os";
5
- import { generateKeypair, keypairFromPrivateKey, decodeToken, sign, verifyAttestation, computeReputation, defaultReputation, PROTOCOL, checkFarming, recordApprovedGain, recordFarmingStrike, loadAuditStore, exportAuditStore, } from "soulprint-core";
5
+ import { generateKeypair, keypairFromPrivateKey, decodeToken, sign, verifyAttestation, computeReputation, defaultReputation, PROTOCOL, PROTOCOL_HASH, isProtocolHashCompatible, checkFarming, recordApprovedGain, recordFarmingStrike, loadAuditStore, exportAuditStore, } from "soulprint-core";
6
6
  import { verifyProof, deserializeProof } from "soulprint-zkp";
7
7
  import { handleCredentialRoute } from "./credentials/index.js";
8
8
  import { publishAttestationP2P, onAttestationReceived, getP2PStats, } from "./p2p.js";
@@ -184,12 +184,17 @@ async function gossipAttestation(att, excludeUrl) {
184
184
  }
185
185
  }
186
186
  // ── Canal 2: HTTP gossip (fallback para nodos legacy) ─────────────────────
187
+ // Incluye X-Protocol-Hash para que el peer receptor valide compatibilidad.
187
188
  const targets = peers.filter(p => p !== excludeUrl);
188
189
  for (const peerUrl of targets) {
189
190
  fetch(`${peerUrl}/reputation/attest`, {
190
191
  method: "POST",
191
- headers: { "Content-Type": "application/json", "X-Gossip": "1" },
192
- body: JSON.stringify({ attestation: att }),
192
+ headers: {
193
+ "Content-Type": "application/json",
194
+ "X-Gossip": "1",
195
+ "X-Protocol-Hash": PROTOCOL_HASH, // ← el receptor valida esto
196
+ },
197
+ body: JSON.stringify({ attestation: att, from_peer: true }),
193
198
  signal: AbortSignal.timeout(GOSSIP_TIMEOUT_MS),
194
199
  }).catch(() => { });
195
200
  }
@@ -248,11 +253,12 @@ function handleInfo(res, nodeKeypair) {
248
253
  node_did: nodeKeypair.did,
249
254
  version: VERSION,
250
255
  protocol: PROTOCOL.VERSION,
256
+ protocol_hash: PROTOCOL_HASH, // ← cualquier modificación cambia este hash
251
257
  total_verified: Object.keys(nullifiers).length,
252
258
  total_reputation: Object.keys(repStore).length,
253
259
  known_peers: peers.length,
254
260
  supported_countries: ["CO"],
255
- capabilities: ["zk-verify", "anti-sybil", "co-sign", "bot-reputation", "p2p-gossipsub"],
261
+ capabilities: ["zk-verify", "anti-sybil", "co-sign", "bot-reputation", "p2p-gossipsub", "credential-validators", "anti-farming"],
256
262
  rate_limit: `${PROTOCOL.RATE_LIMIT_MAX} req/min per IP`,
257
263
  // P2P stats (Phase 5)
258
264
  p2p: p2pStats ? {
@@ -275,6 +281,11 @@ function handleInfo(res, nodeKeypair) {
275
281
  function handleProtocol(res) {
276
282
  json(res, 200, {
277
283
  protocol_version: PROTOCOL.VERSION,
284
+ // ── Protocol Hash — IDENTIDAD DE LA RED ────────────────────────────────
285
+ // Cualquier nodo con un hash diferente es rechazado automáticamente.
286
+ // Si PROTOCOL fue modificado (aunque sea un valor), este hash cambia.
287
+ protocol_hash: PROTOCOL_HASH,
288
+ // ── Score limits ────────────────────────────────────────────────────────
278
289
  score_floor: PROTOCOL.SCORE_FLOOR,
279
290
  verified_score_floor: PROTOCOL.VERIFIED_SCORE_FLOOR,
280
291
  min_attester_score: PROTOCOL.MIN_ATTESTER_SCORE,
@@ -282,11 +293,20 @@ function handleProtocol(res) {
282
293
  reputation_max: PROTOCOL.REPUTATION_MAX,
283
294
  max_score: PROTOCOL.MAX_SCORE,
284
295
  default_reputation: PROTOCOL.DEFAULT_REPUTATION,
296
+ // ── Biometric thresholds ────────────────────────────────────────────────
297
+ face_sim_doc_selfie: PROTOCOL.FACE_SIM_DOC_SELFIE,
298
+ face_sim_selfie_selfie: PROTOCOL.FACE_SIM_SELFIE_SELFIE,
299
+ face_key_dims: PROTOCOL.FACE_KEY_DIMS,
300
+ face_key_precision: PROTOCOL.FACE_KEY_PRECISION,
301
+ // ── Retry / timing ──────────────────────────────────────────────────────
285
302
  verify_retry_max: PROTOCOL.VERIFY_RETRY_MAX,
286
303
  verify_retry_base_ms: PROTOCOL.VERIFY_RETRY_BASE_MS,
287
304
  verify_retry_max_ms: PROTOCOL.VERIFY_RETRY_MAX_MS,
288
305
  att_max_age_seconds: PROTOCOL.ATT_MAX_AGE_SECONDS,
289
- immutable: true, // todas estas constantes son inamovibles por diseño
306
+ // ── Enforcement notice ──────────────────────────────────────────────────
307
+ immutable: true,
308
+ enforcement: "p2p-hash", // ← la red rechaza nodos con hash diferente
309
+ note: "Nodes with a different protocol_hash are rejected by the network. Modifying any constant changes the hash and isolates the node.",
290
310
  });
291
311
  }
292
312
  // ── GET /reputation/:did ──────────────────────────────────────────────────────
@@ -327,6 +347,21 @@ async function handleAttest(req, res, ip) {
327
347
  const { attestation, service_spt, from_peer } = body ?? {};
328
348
  if (!attestation)
329
349
  return json(res, 400, { error: "Missing field: attestation" });
350
+ // ── Protocol Hash Enforcement (gossip desde peers) ────────────────────────
351
+ // Si la attestation viene de un peer (X-Gossip: 1), validamos que el peer
352
+ // opera con las mismas constantes de protocolo.
353
+ // Un nodo con constantes modificadas no puede inyectar attestations en la red.
354
+ if (from_peer) {
355
+ const peerHash = req.headers["x-protocol-hash"];
356
+ if (peerHash && !isProtocolHashCompatible(peerHash)) {
357
+ console.warn(`[protocol] Gossip rechazado de ${ip} — hash incompatible: ${peerHash?.slice(0, 16)}...`);
358
+ return json(res, 409, {
359
+ error: "Protocol mismatch — gossip rejected",
360
+ our_hash: PROTOCOL_HASH,
361
+ their_hash: peerHash,
362
+ });
363
+ }
364
+ }
330
365
  const att = attestation;
331
366
  // ── Validaciones básicas de la attestation ────────────────────────────────
332
367
  if (typeof att.issuer_did !== "string")
@@ -422,16 +457,30 @@ async function handlePeerRegister(req, res) {
422
457
  catch (e) {
423
458
  return json(res, 400, { error: e.message });
424
459
  }
425
- const { url } = body ?? {};
460
+ const { url, protocol_hash } = body ?? {};
426
461
  if (!url || typeof url !== "string")
427
462
  return json(res, 400, { error: "Missing field: url" });
428
463
  if (!/^https?:\/\//.test(url))
429
464
  return json(res, 400, { error: "url must start with http:// or https://" });
465
+ // ── Protocol Hash Enforcement — INAMOVIBLE POR LA RED ────────────────────
466
+ // Si el peer envía un hash, DEBE coincidir con el nuestro.
467
+ // Si no envía hash → se acepta (nodos legacy / primeras versiones).
468
+ // En versiones futuras, el hash será OBLIGATORIO.
469
+ if (protocol_hash && !isProtocolHashCompatible(protocol_hash)) {
470
+ return json(res, 409, {
471
+ error: "Protocol mismatch — node rejected",
472
+ reason: "The peer is running with different protocol constants. This breaks network consensus.",
473
+ our_hash: PROTOCOL_HASH,
474
+ their_hash: protocol_hash,
475
+ our_version: PROTOCOL.VERSION,
476
+ resolution: "Update soulprint-network to the latest version, or join a compatible network.",
477
+ });
478
+ }
430
479
  if (peers.includes(url))
431
480
  return json(res, 200, { ok: true, peers: peers.length, msg: "Already registered" });
432
481
  peers.push(url);
433
482
  savePeers();
434
- json(res, 200, { ok: true, peers: peers.length });
483
+ json(res, 200, { ok: true, peers: peers.length, protocol_hash: PROTOCOL_HASH });
435
484
  }
436
485
  // ── GET /peers ─────────────────────────────────────────────────────────────────
437
486
  function handleGetPeers(res) {
@@ -579,6 +628,8 @@ export function startValidatorNode(port = PORT) {
579
628
  console.log(`\n🌐 Soulprint Validator Node v${VERSION}`);
580
629
  console.log(` Node DID: ${nodeKeypair.did}`);
581
630
  console.log(` Listening: http://0.0.0.0:${port}`);
631
+ console.log(` Protocol: ${PROTOCOL.VERSION} | hash: ${PROTOCOL_HASH.slice(0, 16)}...`);
632
+ console.log(` ⚠️ Hash mismatch with peers → connection rejected (P2P enforcement)`);
582
633
  console.log(` Nullifiers: ${Object.keys(nullifiers).length}`);
583
634
  console.log(` Reputations: ${Object.keys(repStore).length}`);
584
635
  console.log(` Known peers: ${peers.length}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "soulprint-network",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Soulprint validator node — HTTP server that verifies ZK proofs, co-signs SPTs, anti-Sybil registry",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -44,8 +44,8 @@
44
44
  "otpauth": "^9.5.0",
45
45
  "otplib": "^13.3.0",
46
46
  "uint8arrays": "5.1.0",
47
- "soulprint-core": "0.1.5",
48
- "soulprint-zkp": "0.1.3"
47
+ "soulprint-core": "0.1.7",
48
+ "soulprint-zkp": "0.1.4"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "^20.0.0",