soulprint-network 0.3.7 → 0.3.8

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.
@@ -1,7 +1,7 @@
1
1
  {
2
- "codeHash": "46483cb30c07c25eab3484cb4642abfc1c20f6647730edfba82833136a8aa21e",
3
- "codeHashHex": "0x46483cb30c07c25eab3484cb4642abfc1c20f6647730edfba82833136a8aa21e",
4
- "computedAt": "2026-02-24T22:15:27.997Z",
2
+ "codeHash": "cede7ea2b4acfb3c25dd603fa290f41ea12f02394d617857e0c3247f30d0e0a3",
3
+ "codeHashHex": "0xcede7ea2b4acfb3c25dd603fa290f41ea12f02394d617857e0c3247f30d0e0a3",
4
+ "computedAt": "2026-02-24T22:57:27.288Z",
5
5
  "fileCount": 18,
6
6
  "files": [
7
7
  "blockchain/blockchain-anchor.ts",
@@ -13,7 +13,7 @@ export declare function setP2PNode(node: SoulprintP2PNode): void;
13
13
  *
14
14
  * PROTOCOL ENFORCEMENT:
15
15
  * - Si el bot tiene DocumentVerified, su score total nunca puede caer por
16
- * debajo de PROTOCOL.VERIFIED_SCORE_FLOOR (52) inamovible.
16
+ * debajo de PROTOCOL.VERIFIED_SCORE_FLOOR (52) - inamovible.
17
17
  * - Anti-replay: la misma attestation (mismo issuer + timestamp + context)
18
18
  * no se puede aplicar dos veces.
19
19
  *
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, createToken, TOKEN_LIFETIME_SECONDS, TOKEN_RENEW_PREEMPTIVE_SECS, TOKEN_RENEW_GRACE_SECS, TOKEN_RENEW_COOLDOWN_SECS, verifyAttestation, computeReputation, defaultReputation, PROTOCOL, PROTOCOL_HASH, isProtocolHashCompatible, computeTotalScoreWithFloor, checkFarming, recordApprovedGain, recordFarmingStrike, loadAuditStore, exportAuditStore, } from "soulprint-core";
5
+ import { generateKeypair, keypairFromPrivateKey, decodeToken, sign, createToken, TOKEN_LIFETIME_SECONDS, TOKEN_RENEW_PREEMPTIVE_SECS, TOKEN_RENEW_GRACE_SECS, TOKEN_RENEW_COOLDOWN_SECS, NonceStore, verifyAttestation, computeReputation, defaultReputation, PROTOCOL, PROTOCOL_HASH, isProtocolHashCompatible, computeTotalScoreWithFloor, checkFarming, recordApprovedGain, recordFarmingStrike, loadAuditStore, exportAuditStore, } from "soulprint-core";
6
6
  import { verifyProof, deserializeProof } from "soulprint-zkp";
7
7
  import { buildChallengeResponse, verifyPeerBehavior, } from "./peer-challenge.js";
8
8
  import { handleCredentialRoute } from "./credentials/index.js";
@@ -23,11 +23,11 @@ const PEERS_DB = join(NODE_DIR, "peers.json");
23
23
  const AUDIT_DB = join(NODE_DIR, "audit.json");
24
24
  const VERSION = "0.2.0";
25
25
  const MAX_BODY_BYTES = 64 * 1024;
26
- // ── Protocol constants (inamovibles no cambiar directamente aquí) ───────────
26
+ // ── Protocol constants (inamovibles - no cambiar directamente aquí) ───────────
27
27
  const RATE_LIMIT_MS = PROTOCOL.RATE_LIMIT_WINDOW_MS;
28
28
  const RATE_LIMIT_MAX = PROTOCOL.RATE_LIMIT_MAX;
29
29
  const CLOCK_SKEW_MAX = PROTOCOL.CLOCK_SKEW_MAX_SECONDS;
30
- const MIN_ATTESTER_SCORE = PROTOCOL.MIN_ATTESTER_SCORE; // 65 inamovible
30
+ const MIN_ATTESTER_SCORE = PROTOCOL.MIN_ATTESTER_SCORE; // 65 - inamovible
31
31
  const ATT_MAX_AGE_SECONDS = PROTOCOL.ATT_MAX_AGE_SECONDS;
32
32
  const GOSSIP_TIMEOUT_MS = PROTOCOL.GOSSIP_TIMEOUT_MS;
33
33
  // ── P2P Node (Phase 5) ────────────────────────────────────────────────────────
@@ -44,7 +44,7 @@ export function setP2PNode(node) {
44
44
  onAttestationReceived(node, (att, fromPeer) => {
45
45
  // Validar firma antes de aplicar
46
46
  if (!verifyAttestation(att)) {
47
- console.warn(`[p2p] Attestation inválida de peer ${fromPeer.slice(0, 16)}... descartada`);
47
+ console.warn(`[p2p] Attestation inválida de peer ${fromPeer.slice(0, 16)}... - descartada`);
48
48
  return;
49
49
  }
50
50
  // Anti-replay ya está dentro de applyAttestation()
@@ -55,6 +55,8 @@ export function setP2PNode(node) {
55
55
  }
56
56
  // ── Rate limiter ──────────────────────────────────────────────────────────────
57
57
  const rateLimits = new Map();
58
+ // ── DPoP Nonce Store — anti-replay para request signing ──────────────────────
59
+ const dpopNonces = new NonceStore();
58
60
  function checkRateLimit(ip) {
59
61
  const now = Date.now();
60
62
  const entry = rateLimits.get(ip);
@@ -115,7 +117,7 @@ function getReputation(did) {
115
117
  *
116
118
  * PROTOCOL ENFORCEMENT:
117
119
  * - Si el bot tiene DocumentVerified, su score total nunca puede caer por
118
- * debajo de PROTOCOL.VERIFIED_SCORE_FLOOR (52) inamovible.
120
+ * debajo de PROTOCOL.VERIFIED_SCORE_FLOOR (52) - inamovible.
119
121
  * - Anti-replay: la misma attestation (mismo issuer + timestamp + context)
120
122
  * no se puede aplicar dos veces.
121
123
  *
@@ -177,8 +179,8 @@ function savePeers() { writeFileSync(PEERS_DB, JSON.stringify(peers, null, 2));
177
179
  * Gossip: propaga la attestation a la red.
178
180
  *
179
181
  * Estrategia:
180
- * 1. P2P GossipSub (Phase 5) si el nodo libp2p está activo
181
- * 2. HTTP fire-and-forget (Phase 3) fallback para nodos legacy sin libp2p
182
+ * 1. P2P GossipSub (Phase 5) - si el nodo libp2p está activo
183
+ * 2. HTTP fire-and-forget (Phase 3) - fallback para nodos legacy sin libp2p
182
184
  *
183
185
  * Ambos canales son fire-and-forget: no bloquean la respuesta al cliente.
184
186
  */
@@ -296,7 +298,7 @@ function handleInfo(res, nodeKeypair) {
296
298
  function handleProtocol(res) {
297
299
  json(res, 200, {
298
300
  protocol_version: PROTOCOL.VERSION,
299
- // ── Protocol Hash IDENTIDAD DE LA RED ────────────────────────────────
301
+ // ── Protocol Hash - IDENTIDAD DE LA RED ────────────────────────────────
300
302
  // Cualquier nodo con un hash diferente es rechazado automáticamente.
301
303
  // Si PROTOCOL fue modificado (aunque sea un valor), este hash cambia.
302
304
  protocol_hash: PROTOCOL_HASH,
@@ -383,9 +385,9 @@ async function handleAttest(req, res, ip) {
383
385
  if (from_peer) {
384
386
  const peerHash = req.headers["x-protocol-hash"];
385
387
  if (peerHash && !isProtocolHashCompatible(peerHash)) {
386
- console.warn(`[protocol] Gossip rechazado de ${ip} hash incompatible: ${peerHash?.slice(0, 16)}...`);
388
+ console.warn(`[protocol] Gossip rechazado de ${ip} - hash incompatible: ${peerHash?.slice(0, 16)}...`);
387
389
  return json(res, 409, {
388
- error: "Protocol mismatch gossip rejected",
390
+ error: "Protocol mismatch - gossip rejected",
389
391
  our_hash: PROTOCOL_HASH,
390
392
  their_hash: peerHash,
391
393
  });
@@ -415,7 +417,7 @@ async function handleAttest(req, res, ip) {
415
417
  // Si viene del exterior, exigir service_spt
416
418
  if (!from_peer) {
417
419
  if (!service_spt)
418
- return json(res, 401, { error: "Missing service_spt only verified services can attest" });
420
+ return json(res, 401, { error: "Missing service_spt - only verified services can attest" });
419
421
  const serviceTok = decodeToken(service_spt);
420
422
  if (!serviceTok)
421
423
  return json(res, 401, { error: "Invalid or expired service_spt" });
@@ -446,7 +448,7 @@ async function handleAttest(req, res, ip) {
446
448
  const session = {
447
449
  did: att.target_did,
448
450
  startTime: (att.timestamp - 60) * 1000, // estimar inicio de sesión 60s antes
449
- events: [], // no tenemos eventos individuales aquí se evalúa en withTracking()
451
+ events: [], // no tenemos eventos individuales aquí - se evalúa en withTracking()
450
452
  issuerDid: att.issuer_did,
451
453
  };
452
454
  const farmResult = checkFarming(session, prevAtts);
@@ -530,13 +532,13 @@ async function handlePeerRegister(req, res) {
530
532
  return json(res, 400, { error: "Missing field: url" });
531
533
  if (!/^https?:\/\//.test(url))
532
534
  return json(res, 400, { error: "url must start with http:// or https://" });
533
- // ── Protocol Hash Enforcement INAMOVIBLE POR LA RED ────────────────────
535
+ // ── Protocol Hash Enforcement - INAMOVIBLE POR LA RED ────────────────────
534
536
  // Si el peer envía un hash, DEBE coincidir con el nuestro.
535
537
  // Si no envía hash → se acepta (nodos legacy / primeras versiones).
536
538
  // En versiones futuras, el hash será OBLIGATORIO.
537
539
  if (protocol_hash && !isProtocolHashCompatible(protocol_hash)) {
538
540
  return json(res, 409, {
539
- error: "Protocol mismatch node rejected",
541
+ error: "Protocol mismatch - node rejected",
540
542
  reason: "The peer is running with different protocol constants. This breaks network consensus.",
541
543
  our_hash: PROTOCOL_HASH,
542
544
  their_hash: protocol_hash,
@@ -667,7 +669,7 @@ async function handleTokenRenew(req, res, nodeKeypair) {
667
669
  // Decodificar sin verificar expiración (queremos ver el DID aunque esté expirado)
668
670
  const token = decodeToken(body.spt);
669
671
  if (!token)
670
- return json(res, 401, { error: "Invalid SPT cannot decode" });
672
+ return json(res, 401, { error: "Invalid SPT - cannot decode" });
671
673
  const nowSecs = Math.floor(Date.now() / 1000);
672
674
  const secsUntilExpiry = token.expires - nowSecs;
673
675
  const secsAfterExpiry = nowSecs - token.expires;
@@ -683,13 +685,13 @@ async function handleTokenRenew(req, res, nodeKeypair) {
683
685
  if (!inPreemptWindow && !inGraceWindow) {
684
686
  if (!isExpired) {
685
687
  return json(res, 400, {
686
- error: "Token válido no necesita renovación aún",
688
+ error: "Token válido - no necesita renovación aún",
687
689
  expires_in: secsUntilExpiry,
688
690
  renew_after: secsUntilExpiry - RENEW_PREEMPT,
689
691
  });
690
692
  }
691
693
  return json(res, 401, {
692
- error: "Token expirado hace más de 7 días requiere re-verificación completa",
694
+ error: "Token expirado hace más de 7 días - requiere re-verificación completa",
693
695
  expired_ago: secsAfterExpiry,
694
696
  max_grace: RENEW_GRACE,
695
697
  });
@@ -699,7 +701,7 @@ async function handleTokenRenew(req, res, nodeKeypair) {
699
701
  const lastRenew = repStore[token.did]?._lastRenew ?? 0;
700
702
  if (nowSecs - lastRenew < RENEW_COOLDOWN) {
701
703
  return json(res, 429, {
702
- error: "Renovación muy frecuente espera 60s entre renovaciones",
704
+ error: "Renovación muy frecuente - espera 60s entre renovaciones",
703
705
  retry_in: RENEW_COOLDOWN - (nowSecs - lastRenew),
704
706
  });
705
707
  }
@@ -708,7 +710,7 @@ async function handleTokenRenew(req, res, nodeKeypair) {
708
710
  const nullifierEntry = nullifierPair?.[1];
709
711
  if (!nullifierEntry) {
710
712
  return json(res, 403, {
711
- error: "DID no registrado en este nodo requiere re-verificación",
713
+ error: "DID no registrado en este nodo - requiere re-verificación",
712
714
  did: token.did,
713
715
  });
714
716
  }
@@ -721,7 +723,7 @@ async function handleTokenRenew(req, res, nodeKeypair) {
721
723
  const scoreFloor = PROTOCOL.VERIFIED_SCORE_FLOOR ?? 52;
722
724
  if (currentRep < scoreFloor) {
723
725
  return json(res, 403, {
724
- error: "Score por debajo del floor renovación denegada",
726
+ error: "Score por debajo del floor - renovación denegada",
725
727
  score: currentRep,
726
728
  floor: scoreFloor,
727
729
  hint: "El bot necesita más attestations positivas",
@@ -938,7 +940,7 @@ export function startValidatorNode(port = PORT) {
938
940
  return json(res, 200, anchor.getStats());
939
941
  }
940
942
  // ── Consensus P2P endpoints (sin EVM, sin gas) ─────────────────────────
941
- // GET /consensus/state-info handshake para state-sync
943
+ // GET /consensus/state-info - handshake para state-sync
942
944
  if (cleanUrl === "/consensus/state-info" && req.method === "GET") {
943
945
  return json(res, 200, {
944
946
  nullifierCount: nullifierConsensus.getAllNullifiers().length,
@@ -948,7 +950,7 @@ export function startValidatorNode(port = PORT) {
948
950
  nodeVersion: VERSION,
949
951
  });
950
952
  }
951
- // GET /consensus/state?page=N&limit=500&since=TS bulk state sync
953
+ // GET /consensus/state?page=N&limit=500&since=TS - bulk state sync
952
954
  if (cleanUrl === "/consensus/state" && req.method === "GET") {
953
955
  const params = new URLSearchParams(url.split("?")[1] ?? "");
954
956
  const page = parseInt(params.get("page") ?? "0");
@@ -967,7 +969,7 @@ export function startValidatorNode(port = PORT) {
967
969
  protocolHash: PROTOCOL_HASH,
968
970
  });
969
971
  }
970
- // POST /consensus/message recibir mensaje de consenso cifrado
972
+ // POST /consensus/message - recibir mensaje de consenso cifrado
971
973
  if (cleanUrl === "/consensus/message" && req.method === "POST") {
972
974
  const body = await readBody(req);
973
975
  if (!body?.payload)
@@ -990,7 +992,7 @@ export function startValidatorNode(port = PORT) {
990
992
  }
991
993
  }
992
994
  // ── Governance endpoints ──────────────────────────────────────────────────
993
- // GET /governance estado del hash aprobado + propuestas activas
995
+ // GET /governance - estado del hash aprobado + propuestas activas
994
996
  if (cleanUrl === "/governance" && req.method === "GET") {
995
997
  const [currentHash, active, history] = await Promise.all([
996
998
  client?.getCurrentApprovedHash() ?? null,
@@ -1005,14 +1007,14 @@ export function startValidatorNode(port = PORT) {
1005
1007
  nodeCompatible: !currentHash || currentHash.toLowerCase() === ("0x" + PROTOCOL_HASH).toLowerCase(),
1006
1008
  });
1007
1009
  }
1008
- // GET /governance/proposals lista de propuestas activas
1010
+ // GET /governance/proposals - lista de propuestas activas
1009
1011
  if (cleanUrl === "/governance/proposals" && req.method === "GET") {
1010
1012
  if (!client?.isConnected)
1011
1013
  return json(res, 503, { error: "Blockchain not connected" });
1012
1014
  const proposals = await client.getActiveProposals();
1013
1015
  return json(res, 200, { proposals, total: proposals.length });
1014
1016
  }
1015
- // GET /governance/proposal/:id detalle de una propuesta
1017
+ // GET /governance/proposal/:id - detalle de una propuesta
1016
1018
  if (cleanUrl.match(/^\/governance\/proposal\/\d+$/) && req.method === "GET") {
1017
1019
  if (!client?.isConnected)
1018
1020
  return json(res, 503, { error: "Blockchain not connected" });
@@ -1023,7 +1025,7 @@ export function startValidatorNode(port = PORT) {
1023
1025
  const remaining = await client.getTimelockRemaining(proposalId);
1024
1026
  return json(res, 200, { ...proposal, timelockRemainingSeconds: remaining });
1025
1027
  }
1026
- // POST /governance/propose proponer upgrade del PROTOCOL_HASH
1028
+ // POST /governance/propose - proponer upgrade del PROTOCOL_HASH
1027
1029
  // Body: { did, newHash, rationale }
1028
1030
  if (cleanUrl === "/governance/propose" && req.method === "POST") {
1029
1031
  if (!client?.isConnected)
@@ -1038,10 +1040,10 @@ export function startValidatorNode(port = PORT) {
1038
1040
  rationale: body.rationale,
1039
1041
  });
1040
1042
  if (!result)
1041
- return json(res, 500, { error: "Proposal failed check validator logs" });
1043
+ return json(res, 500, { error: "Proposal failed - check validator logs" });
1042
1044
  return json(res, 201, result);
1043
1045
  }
1044
- // POST /governance/vote votar en una propuesta
1046
+ // POST /governance/vote - votar en una propuesta
1045
1047
  // Body: { proposalId, did, approve }
1046
1048
  if (cleanUrl === "/governance/vote" && req.method === "POST") {
1047
1049
  if (!client?.isConnected)
@@ -1056,10 +1058,10 @@ export function startValidatorNode(port = PORT) {
1056
1058
  approve: Boolean(body.approve),
1057
1059
  });
1058
1060
  if (!txHash)
1059
- return json(res, 500, { error: "Vote failed check validator logs" });
1061
+ return json(res, 500, { error: "Vote failed - check validator logs" });
1060
1062
  return json(res, 200, { txHash, proposalId: body.proposalId, approve: body.approve });
1061
1063
  }
1062
- // POST /governance/execute ejecutar propuesta post-timelock
1064
+ // POST /governance/execute - ejecutar propuesta post-timelock
1063
1065
  // Body: { proposalId }
1064
1066
  if (cleanUrl === "/governance/execute" && req.method === "POST") {
1065
1067
  if (!client?.isConnected)
@@ -1069,7 +1071,7 @@ export function startValidatorNode(port = PORT) {
1069
1071
  return json(res, 400, { error: "Required: proposalId" });
1070
1072
  const txHash = await client.executeProposal(Number(body.proposalId));
1071
1073
  if (!txHash)
1072
- return json(res, 500, { error: "Execute failed timelock not expired or proposal not approved" });
1074
+ return json(res, 500, { error: "Execute failed - timelock not expired or proposal not approved" });
1073
1075
  return json(res, 200, { txHash, proposalId: body.proposalId, executed: true });
1074
1076
  }
1075
1077
  json(res, 404, { error: "Not found" });
@@ -1101,7 +1103,7 @@ export function startValidatorNode(port = PORT) {
1101
1103
  console.log(` POST /credentials/phone/verify`);
1102
1104
  console.log(` GET /credentials/github/start → GitHub OAuth (native fetch)`);
1103
1105
  console.log(` GET /credentials/github/callback`);
1104
- console.log(`\n Anti-farming: ON max +1/day, pattern detection, cooldowns`);
1106
+ console.log(`\n Anti-farming: ON - max +1/day, pattern detection, cooldowns`);
1105
1107
  console.log(`\n Consensus P2P (sin EVM, sin gas):`);
1106
1108
  console.log(` GET /consensus/state-info handshake para state-sync`);
1107
1109
  console.log(` GET /consensus/state bulk state sync paginado`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "soulprint-network",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
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",