soulprint-network 0.3.4 → 0.3.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/code-hash.json +3 -3
- package/dist/code-integrity.d.ts +1 -1
- package/dist/code-integrity.js +6 -2
- package/dist/validator.js +107 -1
- package/package.json +3 -3
package/dist/code-hash.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"codeHash": "
|
|
3
|
-
"codeHashHex": "
|
|
4
|
-
"computedAt": "2026-02-
|
|
2
|
+
"codeHash": "9aeeff8b0b506a6e06a6c297165b8b397c0c1652e9e2844e05aabe36c14b490c",
|
|
3
|
+
"codeHashHex": "0x9aeeff8b0b506a6e06a6c297165b8b397c0c1652e9e2844e05aabe36c14b490c",
|
|
4
|
+
"computedAt": "2026-02-24T21:50:18.197Z",
|
|
5
5
|
"fileCount": 17,
|
|
6
6
|
"files": [
|
|
7
7
|
"blockchain/blockchain-anchor.ts",
|
package/dist/code-integrity.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export declare function getCodeIntegrity(): CodeIntegrityInfo;
|
|
|
32
32
|
* @param approvedHashes Lista de hashes aprobados por governance
|
|
33
33
|
* @returns true si el hash actual está en la lista de aprobados
|
|
34
34
|
*/
|
|
35
|
-
export declare function isCodeApproved(approvedHashes: string[]): boolean;
|
|
35
|
+
export declare function isCodeApproved(approvedHashes: (string | null | undefined)[]): boolean;
|
|
36
36
|
/**
|
|
37
37
|
* Computa un hash rápido del propio binario en tiempo real (fallback).
|
|
38
38
|
* Menos preciso que el hash del código fuente pero no requiere build step.
|
package/dist/code-integrity.js
CHANGED
|
@@ -68,9 +68,13 @@ export function getCodeIntegrity() {
|
|
|
68
68
|
export function isCodeApproved(approvedHashes) {
|
|
69
69
|
const info = getCodeIntegrity();
|
|
70
70
|
if (!info.available)
|
|
71
|
-
return false;
|
|
71
|
+
return false;
|
|
72
72
|
const h = info.codeHash.toLowerCase().replace("0x", "");
|
|
73
|
-
return approvedHashes.some(a =>
|
|
73
|
+
return approvedHashes.some(a => {
|
|
74
|
+
if (a == null || typeof a !== "string")
|
|
75
|
+
return false;
|
|
76
|
+
return a.toLowerCase().replace("0x", "") === h;
|
|
77
|
+
});
|
|
74
78
|
}
|
|
75
79
|
/**
|
|
76
80
|
* Computa un hash rápido del propio binario en tiempo real (fallback).
|
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, PROTOCOL_HASH, isProtocolHashCompatible, 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, 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 { handleCredentialRoute } from "./credentials/index.js";
|
|
8
8
|
import { encryptGossip, decryptGossip } from "./crypto/gossip-cipher.js";
|
|
@@ -591,6 +591,109 @@ async function handleVerify(req, res, nodeKeypair, ip) {
|
|
|
591
591
|
verified_at: now,
|
|
592
592
|
});
|
|
593
593
|
}
|
|
594
|
+
// ── POST /token/renew ─────────────────────────────────────────────────────────
|
|
595
|
+
/**
|
|
596
|
+
* Auto-renueva un SPT próximo a expirar o recién expirado.
|
|
597
|
+
*
|
|
598
|
+
* REGLAS:
|
|
599
|
+
* • Token dentro del período de pre-renew (< 1h restante) → renovar
|
|
600
|
+
* • Token expirado hace < 7 días → renovar (grace period)
|
|
601
|
+
* • Token expirado hace > 7 días → denegar, requiere re-verificación completa
|
|
602
|
+
* • Score actual < VERIFIED_SCORE_FLOOR → denegar
|
|
603
|
+
* • Cooldown: un mismo DID no puede renovar más de 1 vez cada 60s
|
|
604
|
+
*
|
|
605
|
+
* Body: { spt: "<token_actual>" }
|
|
606
|
+
* Respuesta: { spt: "<token_nuevo>", expires_in: <segundos>, renewed: true }
|
|
607
|
+
*/
|
|
608
|
+
async function handleTokenRenew(req, res, nodeKeypair) {
|
|
609
|
+
const body = await readBody(req);
|
|
610
|
+
if (!body?.spt)
|
|
611
|
+
return json(res, 400, { error: "Required: spt" });
|
|
612
|
+
// Decodificar sin verificar expiración (queremos ver el DID aunque esté expirado)
|
|
613
|
+
const token = decodeToken(body.spt);
|
|
614
|
+
if (!token)
|
|
615
|
+
return json(res, 401, { error: "Invalid SPT — cannot decode" });
|
|
616
|
+
const nowSecs = Math.floor(Date.now() / 1000);
|
|
617
|
+
const secsUntilExpiry = token.expires - nowSecs;
|
|
618
|
+
const secsAfterExpiry = nowSecs - token.expires;
|
|
619
|
+
// Ventana de renovación permitida
|
|
620
|
+
const TOKEN_LIFETIME = TOKEN_LIFETIME_SECONDS;
|
|
621
|
+
const RENEW_PREEMPT = TOKEN_RENEW_PREEMPTIVE_SECS;
|
|
622
|
+
const RENEW_GRACE = TOKEN_RENEW_GRACE_SECS;
|
|
623
|
+
const RENEW_COOLDOWN = TOKEN_RENEW_COOLDOWN_SECS;
|
|
624
|
+
// ── Verificar ventana de tiempo ──────────────────────────────────────────
|
|
625
|
+
const isExpired = secsUntilExpiry <= 0;
|
|
626
|
+
const inPreemptWindow = !isExpired && secsUntilExpiry <= RENEW_PREEMPT;
|
|
627
|
+
const inGraceWindow = isExpired && secsAfterExpiry <= RENEW_GRACE;
|
|
628
|
+
if (!inPreemptWindow && !inGraceWindow) {
|
|
629
|
+
if (!isExpired) {
|
|
630
|
+
return json(res, 400, {
|
|
631
|
+
error: "Token válido — no necesita renovación aún",
|
|
632
|
+
expires_in: secsUntilExpiry,
|
|
633
|
+
renew_after: secsUntilExpiry - RENEW_PREEMPT,
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
return json(res, 401, {
|
|
637
|
+
error: "Token expirado hace más de 7 días — requiere re-verificación completa",
|
|
638
|
+
expired_ago: secsAfterExpiry,
|
|
639
|
+
max_grace: RENEW_GRACE,
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
// ── Anti-spam: cooldown por DID ──────────────────────────────────────────
|
|
643
|
+
const lastRenewKey = `renew:${token.did}`;
|
|
644
|
+
const lastRenew = repStore[token.did]?._lastRenew ?? 0;
|
|
645
|
+
if (nowSecs - lastRenew < RENEW_COOLDOWN) {
|
|
646
|
+
return json(res, 429, {
|
|
647
|
+
error: "Renovación muy frecuente — espera 60s entre renovaciones",
|
|
648
|
+
retry_in: RENEW_COOLDOWN - (nowSecs - lastRenew),
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
// ── Verificar que el DID sigue registrado en el estado P2P ───────────────
|
|
652
|
+
const nullifierPair = Object.entries(nullifiers).find(([, n]) => n.did === token.did);
|
|
653
|
+
const nullifierEntry = nullifierPair?.[1];
|
|
654
|
+
if (!nullifierEntry) {
|
|
655
|
+
return json(res, 403, {
|
|
656
|
+
error: "DID no registrado en este nodo — requiere re-verificación",
|
|
657
|
+
did: token.did,
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
const nullifierHash = nullifierPair[0];
|
|
661
|
+
// ── Verificar score actual (puede haber bajado desde el último token) ────
|
|
662
|
+
const repEntry = repStore[token.did];
|
|
663
|
+
const currentRep = repEntry
|
|
664
|
+
? computeTotalScoreWithFloor(repEntry.identityScore ?? 0, repEntry.score ?? 0, repEntry.hasDocumentVerified ?? false)
|
|
665
|
+
: 0;
|
|
666
|
+
const scoreFloor = PROTOCOL.VERIFIED_SCORE_FLOOR ?? 52;
|
|
667
|
+
if (currentRep < scoreFloor) {
|
|
668
|
+
return json(res, 403, {
|
|
669
|
+
error: "Score por debajo del floor — renovación denegada",
|
|
670
|
+
score: currentRep,
|
|
671
|
+
floor: scoreFloor,
|
|
672
|
+
hint: "El bot necesita más attestations positivas",
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
// ── Todo OK → emitir nuevo SPT ───────────────────────────────────────────
|
|
676
|
+
// Mantener las mismas credenciales y score del token original
|
|
677
|
+
const newSpt = createToken(nodeKeypair, nullifierHash, // nullifier hash (fuente de verdad)
|
|
678
|
+
(token.credentials ?? []), {
|
|
679
|
+
lifetimeSeconds: TOKEN_LIFETIME,
|
|
680
|
+
country: token.country,
|
|
681
|
+
});
|
|
682
|
+
// Registrar timestamp de renovación (anti-spam)
|
|
683
|
+
if (repStore[token.did]) {
|
|
684
|
+
repStore[token.did]._lastRenew = nowSecs;
|
|
685
|
+
saveReputation();
|
|
686
|
+
}
|
|
687
|
+
console.log(`[renew] ✅ ${token.did.slice(0, 20)}... → nuevo SPT (${isExpired ? "post-grace" : "pre-emptivo"})`);
|
|
688
|
+
return json(res, 200, {
|
|
689
|
+
spt: newSpt,
|
|
690
|
+
expires_in: TOKEN_LIFETIME,
|
|
691
|
+
renewed: true,
|
|
692
|
+
method: isExpired ? "grace_window" : "preemptive",
|
|
693
|
+
old_expired: isExpired,
|
|
694
|
+
node_did: nodeKeypair.did,
|
|
695
|
+
});
|
|
696
|
+
}
|
|
594
697
|
// ── GET /nullifier/:hash ──────────────────────────────────────────────────────
|
|
595
698
|
function handleNullifierCheck(res, nullifier) {
|
|
596
699
|
if (!/^(0x)?[0-9a-fA-F]{1,128}$/.test(nullifier))
|
|
@@ -739,6 +842,8 @@ export function startValidatorNode(port = PORT) {
|
|
|
739
842
|
return handleProtocol(res);
|
|
740
843
|
if (cleanUrl === "/verify" && req.method === "POST")
|
|
741
844
|
return handleVerify(req, res, nodeKeypair, ip);
|
|
845
|
+
if (cleanUrl === "/token/renew" && req.method === "POST")
|
|
846
|
+
return handleTokenRenew(req, res, nodeKeypair);
|
|
742
847
|
if (cleanUrl === "/reputation/attest" && req.method === "POST")
|
|
743
848
|
return handleAttest(req, res, ip);
|
|
744
849
|
if (cleanUrl === "/peers/register" && req.method === "POST")
|
|
@@ -924,6 +1029,7 @@ export function startValidatorNode(port = PORT) {
|
|
|
924
1029
|
console.log(` Known peers: ${peers.length}`);
|
|
925
1030
|
console.log(`\n Core endpoints:`);
|
|
926
1031
|
console.log(` POST /verify verify ZK proof + co-sign`);
|
|
1032
|
+
console.log(` POST /token/renew auto-renew SPT (pre-emptivo 1h / grace 7d)`);
|
|
927
1033
|
console.log(` GET /health code integrity + governance status`);
|
|
928
1034
|
console.log(` GET /info node info`);
|
|
929
1035
|
console.log(` GET /protocol protocol constants (immutable)`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "soulprint-network",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "Soulprint validator node
|
|
3
|
+
"version": "0.3.6",
|
|
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",
|
|
7
7
|
"bin": {
|
|
@@ -68,4 +68,4 @@
|
|
|
68
68
|
"types": "./dist/index.d.ts"
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
-
}
|
|
71
|
+
}
|