soulprint-network 0.4.4 → 0.4.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/blockchain/NullifierRegistryClient.d.ts +46 -0
- package/dist/blockchain/NullifierRegistryClient.js +157 -0
- package/dist/blockchain/ReputationRegistryClient.d.ts +45 -0
- package/dist/blockchain/ReputationRegistryClient.js +151 -0
- package/dist/code-hash.json +6 -4
- package/dist/validator.d.ts +6 -0
- package/dist/validator.js +87 -9
- package/package.json +4 -4
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export declare const NULLIFIER_REGISTRY_RPC = "https://sepolia.base.org";
|
|
2
|
+
export declare const NULLIFIER_REGISTRY_ADDRESS: string;
|
|
3
|
+
export interface NullifierEntry {
|
|
4
|
+
nullifier: string;
|
|
5
|
+
did: string;
|
|
6
|
+
score: number;
|
|
7
|
+
timestamp: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class NullifierRegistryClient {
|
|
10
|
+
private provider;
|
|
11
|
+
private contract;
|
|
12
|
+
private wallet?;
|
|
13
|
+
private cache;
|
|
14
|
+
private cacheAt;
|
|
15
|
+
private cacheTTLMs;
|
|
16
|
+
private address;
|
|
17
|
+
constructor(opts?: {
|
|
18
|
+
rpc?: string;
|
|
19
|
+
address?: string;
|
|
20
|
+
privateKey?: string;
|
|
21
|
+
cacheTTLMs?: number;
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* Sign a nullifier payload with the validator's private key and register on-chain.
|
|
25
|
+
* Non-blocking — logs warning on failure.
|
|
26
|
+
*
|
|
27
|
+
* The contract verifies the ECDSA signature over keccak256(nullifier, did, score).
|
|
28
|
+
*/
|
|
29
|
+
registerNullifier(opts: {
|
|
30
|
+
nullifier: string;
|
|
31
|
+
did: string;
|
|
32
|
+
score?: number;
|
|
33
|
+
}): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Check if a nullifier is registered on-chain.
|
|
36
|
+
*/
|
|
37
|
+
isRegistered(nullifier: string): Promise<boolean>;
|
|
38
|
+
/**
|
|
39
|
+
* Get all registered nullifiers (cached 2 min).
|
|
40
|
+
*/
|
|
41
|
+
getAllNullifiers(): Promise<NullifierEntry[]>;
|
|
42
|
+
refreshNullifiers(): Promise<NullifierEntry[]>;
|
|
43
|
+
getCount(): Promise<number>;
|
|
44
|
+
get contractAddress(): string;
|
|
45
|
+
}
|
|
46
|
+
export declare const nullifierRegistryClient: NullifierRegistryClient;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NullifierRegistryClient
|
|
3
|
+
* On-chain nullifier registry for Soulprint (Base Sepolia).
|
|
4
|
+
*
|
|
5
|
+
* The soulprint.digital validator (authorizedValidator) signs every nullifier
|
|
6
|
+
* before writing it on-chain. Read-only access is public — anyone can verify
|
|
7
|
+
* isRegistered(nullifier) without trust in any third party.
|
|
8
|
+
*
|
|
9
|
+
* Architecture (v0.5.0):
|
|
10
|
+
* - WRITE: only soulprint.digital validator (ADMIN_PRIVATE_KEY)
|
|
11
|
+
* - READ: anyone, cached 2 min
|
|
12
|
+
*/
|
|
13
|
+
import { ethers } from "ethers";
|
|
14
|
+
import { readFileSync } from "node:fs";
|
|
15
|
+
import { join, dirname } from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
export const NULLIFIER_REGISTRY_RPC = "https://sepolia.base.org";
|
|
19
|
+
function loadAddress() {
|
|
20
|
+
try {
|
|
21
|
+
const f = join(__dirname, "addresses.json");
|
|
22
|
+
const d = JSON.parse(readFileSync(f, "utf8"));
|
|
23
|
+
return d["NullifierRegistry"] ?? "";
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return "";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export const NULLIFIER_REGISTRY_ADDRESS = loadAddress();
|
|
30
|
+
const ABI = [
|
|
31
|
+
"function registerNullifier(bytes32 nullifier, string did, uint256 score, bytes sig) external",
|
|
32
|
+
"function isRegistered(bytes32 nullifier) external view returns (bool)",
|
|
33
|
+
"function getNullifier(bytes32 nullifier) external view returns (bytes32 nul, string did, uint256 score, uint256 timestamp)",
|
|
34
|
+
"function getAllNullifiers() external view returns (tuple(bytes32 nullifier, string did, uint256 score, uint256 timestamp)[])",
|
|
35
|
+
"function getNullifierCount() external view returns (uint256)",
|
|
36
|
+
"function authorizedValidator() external view returns (address)",
|
|
37
|
+
"event NullifierRegistered(bytes32 indexed nullifier, string did, uint256 score, uint256 timestamp)",
|
|
38
|
+
];
|
|
39
|
+
export class NullifierRegistryClient {
|
|
40
|
+
provider;
|
|
41
|
+
contract;
|
|
42
|
+
wallet;
|
|
43
|
+
cache = null;
|
|
44
|
+
cacheAt = 0;
|
|
45
|
+
cacheTTLMs;
|
|
46
|
+
address;
|
|
47
|
+
constructor(opts) {
|
|
48
|
+
this.address = opts?.address ?? NULLIFIER_REGISTRY_ADDRESS;
|
|
49
|
+
this.cacheTTLMs = opts?.cacheTTLMs ?? 2 * 60 * 1000; // 2 min
|
|
50
|
+
const rpc = opts?.rpc ?? NULLIFIER_REGISTRY_RPC;
|
|
51
|
+
this.provider = new ethers.JsonRpcProvider(rpc);
|
|
52
|
+
if (!this.address) {
|
|
53
|
+
console.warn("[nullifier-registry] ⚠️ No contract address — registry disabled");
|
|
54
|
+
this.contract = null;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (opts?.privateKey) {
|
|
58
|
+
this.wallet = new ethers.Wallet(opts.privateKey, this.provider);
|
|
59
|
+
this.contract = new ethers.Contract(this.address, ABI, this.wallet);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
this.contract = new ethers.Contract(this.address, ABI, this.provider);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Sign a nullifier payload with the validator's private key and register on-chain.
|
|
67
|
+
* Non-blocking — logs warning on failure.
|
|
68
|
+
*
|
|
69
|
+
* The contract verifies the ECDSA signature over keccak256(nullifier, did, score).
|
|
70
|
+
*/
|
|
71
|
+
async registerNullifier(opts) {
|
|
72
|
+
if (!this.wallet) {
|
|
73
|
+
console.warn("[nullifier-registry] ⚠️ No private key — cannot register nullifier");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!this.address) {
|
|
77
|
+
console.warn("[nullifier-registry] ⚠️ No contract address — skipping registration");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const nullifierBytes = opts.nullifier.startsWith("0x")
|
|
82
|
+
? opts.nullifier
|
|
83
|
+
: `0x${opts.nullifier}`;
|
|
84
|
+
const score = BigInt(opts.score ?? 0);
|
|
85
|
+
// Create the message hash matching what the contract verifies
|
|
86
|
+
const msgHash = ethers.keccak256(ethers.solidityPacked(["bytes32", "string", "uint256"], [nullifierBytes, opts.did, score]));
|
|
87
|
+
// Sign with Ethereum personal_sign prefix (matches contract's \x19Ethereum Signed Message:\n32)
|
|
88
|
+
const sig = await this.wallet.signMessage(ethers.getBytes(msgHash));
|
|
89
|
+
const feeData = await this.provider.getFeeData();
|
|
90
|
+
const tx = await this.contract.registerNullifier(nullifierBytes, opts.did, score, sig, { gasPrice: feeData.gasPrice });
|
|
91
|
+
console.log(`[nullifier-registry] 📡 Registering nullifier on-chain... tx: ${tx.hash}`);
|
|
92
|
+
await tx.wait();
|
|
93
|
+
console.log(`[nullifier-registry] ✅ Nullifier registered: ${nullifierBytes.slice(0, 18)}... did=${opts.did.slice(0, 20)}...`);
|
|
94
|
+
// Invalidate cache
|
|
95
|
+
this.cache = null;
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
console.warn(`[nullifier-registry] ⚠️ Registration failed (non-fatal): ${err.shortMessage ?? err.message}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Check if a nullifier is registered on-chain.
|
|
103
|
+
*/
|
|
104
|
+
async isRegistered(nullifier) {
|
|
105
|
+
if (!this.address)
|
|
106
|
+
return false;
|
|
107
|
+
try {
|
|
108
|
+
const n = nullifier.startsWith("0x") ? nullifier : `0x${nullifier}`;
|
|
109
|
+
return await this.contract.isRegistered(n);
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
console.warn(`[nullifier-registry] ⚠️ isRegistered failed: ${err.shortMessage ?? err.message}`);
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get all registered nullifiers (cached 2 min).
|
|
118
|
+
*/
|
|
119
|
+
async getAllNullifiers() {
|
|
120
|
+
if (this.cache && Date.now() - this.cacheAt < this.cacheTTLMs) {
|
|
121
|
+
return this.cache;
|
|
122
|
+
}
|
|
123
|
+
return this.refreshNullifiers();
|
|
124
|
+
}
|
|
125
|
+
async refreshNullifiers() {
|
|
126
|
+
if (!this.address)
|
|
127
|
+
return [];
|
|
128
|
+
try {
|
|
129
|
+
const raw = await this.contract.getAllNullifiers();
|
|
130
|
+
this.cache = raw.map((e) => ({
|
|
131
|
+
nullifier: e.nullifier,
|
|
132
|
+
did: e.did,
|
|
133
|
+
score: Number(e.score),
|
|
134
|
+
timestamp: Number(e.timestamp),
|
|
135
|
+
}));
|
|
136
|
+
this.cacheAt = Date.now();
|
|
137
|
+
return this.cache;
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
console.warn(`[nullifier-registry] ⚠️ getAllNullifiers failed: ${err.shortMessage ?? err.message}`);
|
|
141
|
+
return this.cache ?? [];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async getCount() {
|
|
145
|
+
if (!this.address)
|
|
146
|
+
return 0;
|
|
147
|
+
try {
|
|
148
|
+
const n = await this.contract.getNullifierCount();
|
|
149
|
+
return Number(n);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return 0;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
get contractAddress() { return this.address; }
|
|
156
|
+
}
|
|
157
|
+
export const nullifierRegistryClient = new NullifierRegistryClient();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export declare const REPUTATION_REGISTRY_RPC = "https://sepolia.base.org";
|
|
2
|
+
export declare const REPUTATION_REGISTRY_ADDRESS: string;
|
|
3
|
+
export interface ScoreEntry {
|
|
4
|
+
did: string;
|
|
5
|
+
score: number;
|
|
6
|
+
context: string;
|
|
7
|
+
updatedAt: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class ReputationRegistryClient {
|
|
10
|
+
private provider;
|
|
11
|
+
private contract;
|
|
12
|
+
private wallet?;
|
|
13
|
+
private cache;
|
|
14
|
+
private cacheAt;
|
|
15
|
+
private cacheTTLMs;
|
|
16
|
+
private address;
|
|
17
|
+
constructor(opts?: {
|
|
18
|
+
rpc?: string;
|
|
19
|
+
address?: string;
|
|
20
|
+
privateKey?: string;
|
|
21
|
+
cacheTTLMs?: number;
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* Set or update a reputation score for a DID.
|
|
25
|
+
* Only works if the wallet is the authorized validator.
|
|
26
|
+
* Non-blocking — logs warning on failure.
|
|
27
|
+
*/
|
|
28
|
+
setScore(opts: {
|
|
29
|
+
did: string;
|
|
30
|
+
score: number;
|
|
31
|
+
context?: string;
|
|
32
|
+
}): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Get reputation score for a specific DID.
|
|
35
|
+
*/
|
|
36
|
+
getScore(did: string): Promise<ScoreEntry | null>;
|
|
37
|
+
/**
|
|
38
|
+
* Get all DID scores (cached 2 min).
|
|
39
|
+
*/
|
|
40
|
+
getAllScores(): Promise<ScoreEntry[]>;
|
|
41
|
+
refreshScores(): Promise<ScoreEntry[]>;
|
|
42
|
+
getCount(): Promise<number>;
|
|
43
|
+
get contractAddress(): string;
|
|
44
|
+
}
|
|
45
|
+
export declare const reputationRegistryClient: ReputationRegistryClient;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReputationRegistryClient
|
|
3
|
+
* On-chain reputation scores for DIDs (Base Sepolia).
|
|
4
|
+
*
|
|
5
|
+
* Only the soulprint.digital validator (authorizedValidator) can write scores.
|
|
6
|
+
* Anyone can read scores from the public ledger.
|
|
7
|
+
*
|
|
8
|
+
* Architecture (v0.5.0):
|
|
9
|
+
* - WRITE: only soulprint.digital validator wallet (ADMIN_PRIVATE_KEY)
|
|
10
|
+
* - READ: anyone, cached 2 min
|
|
11
|
+
*/
|
|
12
|
+
import { ethers } from "ethers";
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
import { join, dirname } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
export const REPUTATION_REGISTRY_RPC = "https://sepolia.base.org";
|
|
18
|
+
function loadAddress() {
|
|
19
|
+
try {
|
|
20
|
+
const f = join(__dirname, "addresses.json");
|
|
21
|
+
const d = JSON.parse(readFileSync(f, "utf8"));
|
|
22
|
+
return d["ReputationRegistry"] ?? "";
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return "";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export const REPUTATION_REGISTRY_ADDRESS = loadAddress();
|
|
29
|
+
const ABI = [
|
|
30
|
+
"function setScore(string did, uint256 score, string context) external",
|
|
31
|
+
"function getScore(string did) external view returns (string retDid, uint256 score, string context, uint256 updatedAt)",
|
|
32
|
+
"function getAllScores() external view returns (tuple(string did, uint256 score, string context, uint256 updatedAt)[])",
|
|
33
|
+
"function getScoreCount() external view returns (uint256)",
|
|
34
|
+
"function authorizedValidators(address) external view returns (bool)",
|
|
35
|
+
"event ScoreUpdated(string indexed did, uint256 score, string context, uint256 updatedAt)",
|
|
36
|
+
];
|
|
37
|
+
export class ReputationRegistryClient {
|
|
38
|
+
provider;
|
|
39
|
+
contract;
|
|
40
|
+
wallet;
|
|
41
|
+
cache = null;
|
|
42
|
+
cacheAt = 0;
|
|
43
|
+
cacheTTLMs;
|
|
44
|
+
address;
|
|
45
|
+
constructor(opts) {
|
|
46
|
+
this.address = opts?.address ?? REPUTATION_REGISTRY_ADDRESS;
|
|
47
|
+
this.cacheTTLMs = opts?.cacheTTLMs ?? 2 * 60 * 1000; // 2 min
|
|
48
|
+
const rpc = opts?.rpc ?? REPUTATION_REGISTRY_RPC;
|
|
49
|
+
this.provider = new ethers.JsonRpcProvider(rpc);
|
|
50
|
+
if (!this.address) {
|
|
51
|
+
console.warn("[reputation-registry] ⚠️ No contract address — registry disabled");
|
|
52
|
+
this.contract = null;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (opts?.privateKey) {
|
|
56
|
+
this.wallet = new ethers.Wallet(opts.privateKey, this.provider);
|
|
57
|
+
this.contract = new ethers.Contract(this.address, ABI, this.wallet);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
this.contract = new ethers.Contract(this.address, ABI, this.provider);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Set or update a reputation score for a DID.
|
|
65
|
+
* Only works if the wallet is the authorized validator.
|
|
66
|
+
* Non-blocking — logs warning on failure.
|
|
67
|
+
*/
|
|
68
|
+
async setScore(opts) {
|
|
69
|
+
if (!this.wallet) {
|
|
70
|
+
console.warn("[reputation-registry] ⚠️ No private key — cannot write score");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!this.address) {
|
|
74
|
+
console.warn("[reputation-registry] ⚠️ No contract address — skipping");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const feeData = await this.provider.getFeeData();
|
|
79
|
+
const tx = await this.contract.setScore(opts.did, BigInt(Math.round(opts.score)), opts.context ?? "soulprint:v1", { gasPrice: feeData.gasPrice });
|
|
80
|
+
console.log(`[reputation-registry] 📡 Setting score on-chain... tx: ${tx.hash}`);
|
|
81
|
+
await tx.wait();
|
|
82
|
+
console.log(`[reputation-registry] ✅ Score set: did=${opts.did.slice(0, 20)}... score=${opts.score}`);
|
|
83
|
+
// Invalidate cache
|
|
84
|
+
this.cache = null;
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
console.warn(`[reputation-registry] ⚠️ setScore failed (non-fatal): ${err.shortMessage ?? err.message}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get reputation score for a specific DID.
|
|
92
|
+
*/
|
|
93
|
+
async getScore(did) {
|
|
94
|
+
if (!this.address)
|
|
95
|
+
return null;
|
|
96
|
+
try {
|
|
97
|
+
const r = await this.contract.getScore(did);
|
|
98
|
+
return {
|
|
99
|
+
did: r.retDid,
|
|
100
|
+
score: Number(r.score),
|
|
101
|
+
context: r.context,
|
|
102
|
+
updatedAt: Number(r.updatedAt),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
console.warn(`[reputation-registry] ⚠️ getScore failed: ${err.shortMessage ?? err.message}`);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get all DID scores (cached 2 min).
|
|
112
|
+
*/
|
|
113
|
+
async getAllScores() {
|
|
114
|
+
if (this.cache && Date.now() - this.cacheAt < this.cacheTTLMs) {
|
|
115
|
+
return this.cache;
|
|
116
|
+
}
|
|
117
|
+
return this.refreshScores();
|
|
118
|
+
}
|
|
119
|
+
async refreshScores() {
|
|
120
|
+
if (!this.address)
|
|
121
|
+
return [];
|
|
122
|
+
try {
|
|
123
|
+
const raw = await this.contract.getAllScores();
|
|
124
|
+
this.cache = raw.map((e) => ({
|
|
125
|
+
did: e.did,
|
|
126
|
+
score: Number(e.score),
|
|
127
|
+
context: e.context,
|
|
128
|
+
updatedAt: Number(e.updatedAt),
|
|
129
|
+
}));
|
|
130
|
+
this.cacheAt = Date.now();
|
|
131
|
+
return this.cache;
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
console.warn(`[reputation-registry] ⚠️ getAllScores failed: ${err.shortMessage ?? err.message}`);
|
|
135
|
+
return this.cache ?? [];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async getCount() {
|
|
139
|
+
if (!this.address)
|
|
140
|
+
return 0;
|
|
141
|
+
try {
|
|
142
|
+
const n = await this.contract.getScoreCount();
|
|
143
|
+
return Number(n);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
get contractAddress() { return this.address; }
|
|
150
|
+
}
|
|
151
|
+
export const reputationRegistryClient = new ReputationRegistryClient();
|
package/dist/code-hash.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
-
"codeHash": "
|
|
3
|
-
"codeHashHex": "
|
|
4
|
-
"computedAt": "2026-03-
|
|
5
|
-
"fileCount":
|
|
2
|
+
"codeHash": "c256c2701bea12bbb2f50552b268bda6065a58d187f7bde6d6be86d82eb73f06",
|
|
3
|
+
"codeHashHex": "0xc256c2701bea12bbb2f50552b268bda6065a58d187f7bde6d6be86d82eb73f06",
|
|
4
|
+
"computedAt": "2026-03-01T03:11:08.326Z",
|
|
5
|
+
"fileCount": 24,
|
|
6
6
|
"files": [
|
|
7
|
+
"blockchain/NullifierRegistryClient.ts",
|
|
7
8
|
"blockchain/PeerRegistryClient.ts",
|
|
9
|
+
"blockchain/ReputationRegistryClient.ts",
|
|
8
10
|
"blockchain/blockchain-anchor.ts",
|
|
9
11
|
"blockchain/blockchain-client.ts",
|
|
10
12
|
"blockchain/protocol-thresholds-client.ts",
|
package/dist/validator.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { IncomingMessage, ServerResponse } from "node:http";
|
|
|
2
2
|
import { BotAttestation, BotReputation } from "soulprint-core";
|
|
3
3
|
import { type SoulprintP2PNode } from "./p2p.js";
|
|
4
4
|
import { PeerRegistryClient } from "./blockchain/PeerRegistryClient.js";
|
|
5
|
+
import { NullifierRegistryClient } from "./blockchain/NullifierRegistryClient.js";
|
|
6
|
+
import { ReputationRegistryClient } from "./blockchain/ReputationRegistryClient.js";
|
|
5
7
|
export declare function getLiveThresholds(): {
|
|
6
8
|
SCORE_FLOOR: number;
|
|
7
9
|
VERIFIED_SCORE_FLOOR: number;
|
|
@@ -14,6 +16,10 @@ export declare function getLiveThresholds(): {
|
|
|
14
16
|
source: "blockchain" | "local_fallback";
|
|
15
17
|
};
|
|
16
18
|
export declare function setPeerRegistryClient(client: PeerRegistryClient): void;
|
|
19
|
+
export declare function setNullifierRegistry(client: NullifierRegistryClient): void;
|
|
20
|
+
export declare function setReputationRegistry(client: ReputationRegistryClient): void;
|
|
21
|
+
export declare function getNullifierRegistry(): NullifierRegistryClient | null;
|
|
22
|
+
export declare function getReputationRegistry(): ReputationRegistryClient | null;
|
|
17
23
|
/**
|
|
18
24
|
* Inyecta el nodo libp2p al validador.
|
|
19
25
|
* Cuando se llama:
|
package/dist/validator.js
CHANGED
|
@@ -76,6 +76,23 @@ let peerRegistryClient = null;
|
|
|
76
76
|
export function setPeerRegistryClient(client) {
|
|
77
77
|
peerRegistryClient = client;
|
|
78
78
|
}
|
|
79
|
+
// ── On-Chain Registries (v0.5.0) ───────────────────────────────────────────────
|
|
80
|
+
// The blockchain IS the shared state. These are the single source of truth.
|
|
81
|
+
// Only soulprint.digital validator can WRITE; anyone can READ.
|
|
82
|
+
let nullifierRegistry = null;
|
|
83
|
+
let reputationRegistry = null;
|
|
84
|
+
export function setNullifierRegistry(client) {
|
|
85
|
+
nullifierRegistry = client;
|
|
86
|
+
}
|
|
87
|
+
export function setReputationRegistry(client) {
|
|
88
|
+
reputationRegistry = client;
|
|
89
|
+
}
|
|
90
|
+
export function getNullifierRegistry() {
|
|
91
|
+
return nullifierRegistry;
|
|
92
|
+
}
|
|
93
|
+
export function getReputationRegistry() {
|
|
94
|
+
return reputationRegistry;
|
|
95
|
+
}
|
|
79
96
|
/**
|
|
80
97
|
* Inyecta el nodo libp2p al validador.
|
|
81
98
|
* Cuando se llama:
|
|
@@ -205,6 +222,14 @@ function applyAttestation(att) {
|
|
|
205
222
|
hasDocumentVerified: hasDocument,
|
|
206
223
|
};
|
|
207
224
|
saveReputation();
|
|
225
|
+
// ── Write reputation to on-chain ReputationRegistry (v0.5.0) — non-blocking
|
|
226
|
+
if (reputationRegistry) {
|
|
227
|
+
reputationRegistry.setScore({
|
|
228
|
+
did: att.target_did,
|
|
229
|
+
score: finalRepScore,
|
|
230
|
+
context: "soulprint:v1",
|
|
231
|
+
}).catch(e => console.warn("[reputation-registry] ⚠️ On-chain write failed:", e.message));
|
|
232
|
+
}
|
|
208
233
|
return { score: finalRepScore, attestations: allAtts.length, last_updated: rep.last_updated };
|
|
209
234
|
}
|
|
210
235
|
// ── P2P state sync metadata ───────────────────────────────────────────────────
|
|
@@ -692,6 +717,17 @@ async function handleVerify(req, res, nodeKeypair, ip) {
|
|
|
692
717
|
else {
|
|
693
718
|
nullifiers[zkResult.nullifier] = { did: token.did, verified_at: now };
|
|
694
719
|
saveNullifiers();
|
|
720
|
+
// ── Write to on-chain NullifierRegistry (v0.5.0) — non-blocking ──────────
|
|
721
|
+
// soulprint.digital validator signs and certifies the identity on-chain.
|
|
722
|
+
// Anyone can now verify isRegistered(nullifier) without trusting this node.
|
|
723
|
+
if (nullifierRegistry) {
|
|
724
|
+
const score = repStore[token.did]?.score ?? 0;
|
|
725
|
+
nullifierRegistry.registerNullifier({
|
|
726
|
+
nullifier: zkResult.nullifier,
|
|
727
|
+
did: token.did,
|
|
728
|
+
score,
|
|
729
|
+
}).catch(e => console.warn("[nullifier-registry] ⚠️ On-chain write failed:", e.message));
|
|
730
|
+
}
|
|
695
731
|
}
|
|
696
732
|
const coSig = sign({ nullifier: zkResult.nullifier, did: token.did, timestamp: now }, nodeKeypair.privateKey);
|
|
697
733
|
// Incluir reputación actual del bot en la respuesta
|
|
@@ -1050,6 +1086,8 @@ export function startValidatorNode(port = PORT) {
|
|
|
1050
1086
|
const httpPeers = peers.length;
|
|
1051
1087
|
const libp2pPeers = p2pStats?.peers ?? 0;
|
|
1052
1088
|
let registeredPeers = 0;
|
|
1089
|
+
let nullifiersOnchain = 0;
|
|
1090
|
+
let reputationOnchain = 0;
|
|
1053
1091
|
try {
|
|
1054
1092
|
if (peerRegistryClient) {
|
|
1055
1093
|
const chainPeers = await peerRegistryClient.getAllPeers();
|
|
@@ -1057,24 +1095,36 @@ export function startValidatorNode(port = PORT) {
|
|
|
1057
1095
|
}
|
|
1058
1096
|
}
|
|
1059
1097
|
catch { /* non-fatal */ }
|
|
1098
|
+
try {
|
|
1099
|
+
if (nullifierRegistry)
|
|
1100
|
+
nullifiersOnchain = await nullifierRegistry.getCount();
|
|
1101
|
+
}
|
|
1102
|
+
catch { /* non-fatal */ }
|
|
1103
|
+
try {
|
|
1104
|
+
if (reputationRegistry)
|
|
1105
|
+
reputationOnchain = await reputationRegistry.getCount();
|
|
1106
|
+
}
|
|
1107
|
+
catch { /* non-fatal */ }
|
|
1060
1108
|
return json(res, 200, {
|
|
1061
1109
|
node_did: nodeKeypair.did.slice(0, 20) + "...",
|
|
1062
1110
|
version: VERSION,
|
|
1063
1111
|
protocol_hash: PROTOCOL_HASH.slice(0, 16) + "...",
|
|
1064
|
-
// identidades y reputación
|
|
1112
|
+
// identidades y reputación (in-memory cache)
|
|
1065
1113
|
verified_identities: Object.keys(nullifiers).length,
|
|
1066
1114
|
reputation_profiles: Object.keys(repStore).length,
|
|
1067
|
-
//
|
|
1115
|
+
// on-chain state (v0.5.0) — blockchain IS the shared state
|
|
1116
|
+
nullifiers_onchain: nullifiersOnchain,
|
|
1117
|
+
reputation_onchain: reputationOnchain,
|
|
1118
|
+
// peers — HTTP gossip
|
|
1068
1119
|
known_peers: httpPeers,
|
|
1069
|
-
// peers — libp2p P2P
|
|
1120
|
+
// peers — libp2p P2P
|
|
1070
1121
|
p2p_peers: libp2pPeers,
|
|
1071
1122
|
p2p_pubsub_peers: p2pStats?.pubsubPeers ?? 0,
|
|
1072
1123
|
p2p_enabled: !!p2pNode,
|
|
1073
|
-
// total = max de ambas capas (HTTP gossip es el piso mínimo garantizado)
|
|
1074
1124
|
total_peers: Math.max(httpPeers, libp2pPeers),
|
|
1075
1125
|
// on-chain registered peers (PeerRegistry)
|
|
1076
1126
|
registered_peers: registeredPeers,
|
|
1077
|
-
// state sync (v0.
|
|
1127
|
+
// state sync (v0.5.0 — blockchain is source of truth)
|
|
1078
1128
|
state_hash: computeHash(Object.keys(nullifiers)).slice(0, 16) + "...",
|
|
1079
1129
|
last_sync: lastSyncTs,
|
|
1080
1130
|
// estado general
|
|
@@ -1085,23 +1135,44 @@ export function startValidatorNode(port = PORT) {
|
|
|
1085
1135
|
}
|
|
1086
1136
|
if (cleanUrl === "/verify" && req.method === "POST")
|
|
1087
1137
|
return handleVerify(req, res, nodeKeypair, ip);
|
|
1088
|
-
// ── State
|
|
1089
|
-
// GET /state/hash —
|
|
1138
|
+
// ── State endpoints (v0.5.0) — blockchain IS the shared state ────────────
|
|
1139
|
+
// GET /state/hash — hash of on-chain nullifier count + reputation count
|
|
1090
1140
|
if (cleanUrl === "/state/hash" && req.method === "GET") {
|
|
1091
1141
|
const currentNullifiers = Object.keys(nullifiers);
|
|
1092
|
-
|
|
1142
|
+
let onchainNullifiers = currentNullifiers.length;
|
|
1143
|
+
let onchainReputation = Object.keys(repStore).length;
|
|
1144
|
+
try {
|
|
1145
|
+
if (nullifierRegistry)
|
|
1146
|
+
onchainNullifiers = await nullifierRegistry.getCount();
|
|
1147
|
+
}
|
|
1148
|
+
catch { }
|
|
1149
|
+
try {
|
|
1150
|
+
if (reputationRegistry)
|
|
1151
|
+
onchainReputation = await reputationRegistry.getCount();
|
|
1152
|
+
}
|
|
1153
|
+
catch { }
|
|
1154
|
+
// Hash includes on-chain counts for consensus
|
|
1155
|
+
const hash = computeHash([...currentNullifiers, `onchain:${onchainNullifiers}:${onchainReputation}`]);
|
|
1093
1156
|
return json(res, 200, {
|
|
1094
1157
|
hash,
|
|
1095
1158
|
nullifier_count: currentNullifiers.length,
|
|
1159
|
+
nullifier_count_onchain: onchainNullifiers,
|
|
1096
1160
|
reputation_count: Object.keys(repStore).length,
|
|
1161
|
+
reputation_count_onchain: onchainReputation,
|
|
1097
1162
|
attestation_count: Object.values(repStore).reduce((n, e) => n + (e.attestations?.length ?? 0), 0),
|
|
1098
1163
|
timestamp: Date.now(),
|
|
1099
1164
|
});
|
|
1100
1165
|
}
|
|
1101
|
-
// GET /state/export — full state export
|
|
1166
|
+
// GET /state/export — full state export including on-chain data
|
|
1102
1167
|
if (cleanUrl === "/state/export" && req.method === "GET") {
|
|
1103
1168
|
const allAttestations = Object.values(repStore).flatMap(e => e.attestations ?? []);
|
|
1169
|
+
// Read on-chain data (cached — won't hit RPC on every call)
|
|
1170
|
+
const [onchainNullifiers, onchainScores] = await Promise.all([
|
|
1171
|
+
nullifierRegistry ? nullifierRegistry.getAllNullifiers().catch(() => []) : Promise.resolve([]),
|
|
1172
|
+
reputationRegistry ? reputationRegistry.getAllScores().catch(() => []) : Promise.resolve([]),
|
|
1173
|
+
]);
|
|
1104
1174
|
return json(res, 200, {
|
|
1175
|
+
// Local in-memory state
|
|
1105
1176
|
nullifiers: Object.keys(nullifiers),
|
|
1106
1177
|
reputation: Object.fromEntries(Object.entries(repStore).map(([did, e]) => [did, e.score])),
|
|
1107
1178
|
attestations: allAttestations,
|
|
@@ -1109,6 +1180,13 @@ export function startValidatorNode(port = PORT) {
|
|
|
1109
1180
|
lastSync: lastSyncTs,
|
|
1110
1181
|
stateHash: computeHash(Object.keys(nullifiers)),
|
|
1111
1182
|
timestamp: Date.now(),
|
|
1183
|
+
// On-chain state (v0.5.0) — canonical source of truth
|
|
1184
|
+
onchain: {
|
|
1185
|
+
nullifiers: onchainNullifiers,
|
|
1186
|
+
reputation: onchainScores,
|
|
1187
|
+
nullifier_count: onchainNullifiers.length,
|
|
1188
|
+
reputation_count: onchainScores.length,
|
|
1189
|
+
},
|
|
1112
1190
|
});
|
|
1113
1191
|
}
|
|
1114
1192
|
// POST /state/merge — merge partial state from a peer
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "soulprint-network",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "Soulprint validator node
|
|
3
|
+
"version": "0.4.6",
|
|
4
|
+
"description": "Soulprint validator node \u2014 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": {
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"nodemailer": "^8.0.1",
|
|
52
52
|
"otpauth": "^9.5.0",
|
|
53
53
|
"otplib": "^13.3.0",
|
|
54
|
-
"soulprint-core": "
|
|
55
|
-
"soulprint-zkp": "
|
|
54
|
+
"soulprint-core": "0.1.11",
|
|
55
|
+
"soulprint-zkp": "0.1.6",
|
|
56
56
|
"uint8arrays": "5.1.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|