soulprint-core 0.1.1 → 0.1.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.
- package/dist/index.d.ts +61 -30
- package/dist/index.js +83 -54
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -3,10 +3,43 @@ export interface SoulprintKeypair {
|
|
|
3
3
|
publicKey: Uint8Array;
|
|
4
4
|
privateKey: Uint8Array;
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* Una attestation es un +1 o -1 emitido por un servicio verificado
|
|
8
|
+
* contra el DID de un bot específico.
|
|
9
|
+
*
|
|
10
|
+
* Solo servicios con score >= 60 pueden emitir attestations válidas.
|
|
11
|
+
* La firma Ed25519 vincula la attestation al DID del emisor.
|
|
12
|
+
*/
|
|
13
|
+
export interface BotAttestation {
|
|
14
|
+
issuer_did: string;
|
|
15
|
+
target_did: string;
|
|
16
|
+
value: 1 | -1;
|
|
17
|
+
context: string;
|
|
18
|
+
timestamp: number;
|
|
19
|
+
sig: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Snapshot de reputación de un bot — se incluye en el SPT como campo `bot_rep`.
|
|
23
|
+
*
|
|
24
|
+
* score: 0-20 (empieza en 10 — neutral)
|
|
25
|
+
* +1 por cada attestation positiva recibida de servicio verificado
|
|
26
|
+
* -1 por cada attestation negativa
|
|
27
|
+
* Clamped: nunca < 0 ni > 20
|
|
28
|
+
*
|
|
29
|
+
* Sin identidad humana: el bot puede tener hasta 20 puntos sólo por reputación.
|
|
30
|
+
* Con identidad humana: identidad (0-80) + reputación (0-20) = 100 total.
|
|
31
|
+
*/
|
|
32
|
+
export interface BotReputation {
|
|
33
|
+
score: number;
|
|
34
|
+
attestations: number;
|
|
35
|
+
last_updated: number;
|
|
36
|
+
}
|
|
6
37
|
export interface SoulprintToken {
|
|
7
38
|
sip: "1";
|
|
8
39
|
did: string;
|
|
9
40
|
score: number;
|
|
41
|
+
identity_score: number;
|
|
42
|
+
bot_rep: BotReputation;
|
|
10
43
|
level: TrustLevel;
|
|
11
44
|
country?: string;
|
|
12
45
|
credentials: CredentialType[];
|
|
@@ -18,54 +51,52 @@ export interface SoulprintToken {
|
|
|
18
51
|
}
|
|
19
52
|
export type TrustLevel = "Unverified" | "EmailVerified" | "PhoneVerified" | "KYCLite" | "KYCFull";
|
|
20
53
|
export type CredentialType = "EmailVerified" | "PhoneVerified" | "GitHubLinked" | "DocumentVerified" | "FaceMatch" | "BiometricBound";
|
|
21
|
-
/**
|
|
22
|
-
* Genera un keypair Ed25519 y construye un did:key
|
|
23
|
-
* El DID es portable — mismo keypair = mismo DID en cualquier dispositivo
|
|
24
|
-
*/
|
|
25
54
|
export declare function generateKeypair(): SoulprintKeypair;
|
|
26
|
-
/**
|
|
27
|
-
* Reconstruye el keypair desde una llave privada guardada
|
|
28
|
-
*/
|
|
29
55
|
export declare function keypairFromPrivateKey(privateKey: Uint8Array): SoulprintKeypair;
|
|
30
|
-
/**
|
|
31
|
-
* Deriva el nullifier desde datos del documento + embedding de cara cuantizado.
|
|
32
|
-
* Determinístico: mismo documento + misma cara = mismo nullifier en cualquier dispositivo.
|
|
33
|
-
* Resistente a Sybil: misma cédula no puede generar dos nullifiers distintos
|
|
34
|
-
* si el face embedding se mantiene.
|
|
35
|
-
*
|
|
36
|
-
* @param cedulaNumber Número de cédula colombiana
|
|
37
|
-
* @param fechaNacimiento YYYY-MM-DD
|
|
38
|
-
* @param faceEmbedding Float32Array (512 dims de InsightFace), cuantizado a 2 decimales
|
|
39
|
-
*/
|
|
40
56
|
export declare function deriveNullifier(cedulaNumber: string, fechaNacimiento: string, faceEmbedding: Float32Array): string;
|
|
57
|
+
export declare function quantizeEmbedding(embedding: Float32Array, precision: number): Float32Array;
|
|
58
|
+
export declare function sign(payload: object, privateKey: Uint8Array): string;
|
|
59
|
+
export declare function verify(payload: object, signature: string, did: string): boolean;
|
|
41
60
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
61
|
+
* Crea una attestation firmada desde un servicio verificado hacia un bot.
|
|
62
|
+
*
|
|
63
|
+
* @param serviceKeypair - Keypair del servicio que emite (ej: MCP de Uber)
|
|
64
|
+
* @param targetDid - DID del bot que recibe la calificación
|
|
65
|
+
* @param value - +1 buen comportamiento, -1 mal comportamiento
|
|
66
|
+
* @param context - Descriptor: "payment-completed", "spam-detected", etc.
|
|
45
67
|
*/
|
|
46
|
-
export declare function
|
|
68
|
+
export declare function createAttestation(serviceKeypair: SoulprintKeypair, targetDid: string, value: 1 | -1, context: string): BotAttestation;
|
|
47
69
|
/**
|
|
48
|
-
*
|
|
70
|
+
* Verifica la firma de una attestation.
|
|
71
|
+
* Retorna false si la firma no coincide con el issuer_did.
|
|
49
72
|
*/
|
|
50
|
-
export declare function
|
|
73
|
+
export declare function verifyAttestation(att: BotAttestation): boolean;
|
|
51
74
|
/**
|
|
52
|
-
*
|
|
75
|
+
* Aplica una lista de attestations verificadas a una reputación base.
|
|
76
|
+
* El score se clampea a [0, 20] y empieza en 10 (neutral).
|
|
77
|
+
*
|
|
78
|
+
* Solo attestations con firma válida son consideradas.
|
|
79
|
+
*
|
|
80
|
+
* @param attestations - Lista de attestations recibidas
|
|
81
|
+
* @param base - Score inicial (default: 10)
|
|
53
82
|
*/
|
|
54
|
-
export declare function
|
|
83
|
+
export declare function computeReputation(attestations: BotAttestation[], base?: number): BotReputation;
|
|
55
84
|
/**
|
|
56
|
-
*
|
|
57
|
-
* Lifetime por defecto: 24 horas.
|
|
85
|
+
* Reputación neutral por defecto — para bots sin historial.
|
|
58
86
|
*/
|
|
87
|
+
export declare function defaultReputation(): BotReputation;
|
|
59
88
|
export declare function createToken(keypair: SoulprintKeypair, nullifier: string, credentials: CredentialType[], options?: {
|
|
60
89
|
lifetimeSeconds?: number;
|
|
61
90
|
country?: string;
|
|
62
91
|
zkProof?: string;
|
|
92
|
+
bot_rep?: BotReputation;
|
|
63
93
|
}): string;
|
|
64
|
-
/**
|
|
65
|
-
* Decodifica y verifica un SPT. Retorna null si es inválido o expirado.
|
|
66
|
-
*/
|
|
67
94
|
export declare function decodeToken(spt: string): (SoulprintToken & {
|
|
68
95
|
sig: string;
|
|
69
96
|
}) | null;
|
|
70
97
|
export declare function calculateScore(credentials: CredentialType[]): number;
|
|
98
|
+
/**
|
|
99
|
+
* Score total = identidad (0-80) + reputación bot (0-20) = 0-100
|
|
100
|
+
*/
|
|
101
|
+
export declare function calculateTotalScore(credentials: CredentialType[], botRep?: BotReputation): number;
|
|
71
102
|
export declare function calculateLevel(credentials: CredentialType[]): TrustLevel;
|
package/dist/index.js
CHANGED
|
@@ -9,89 +9,56 @@ exports.deriveNullifier = deriveNullifier;
|
|
|
9
9
|
exports.quantizeEmbedding = quantizeEmbedding;
|
|
10
10
|
exports.sign = sign;
|
|
11
11
|
exports.verify = verify;
|
|
12
|
+
exports.createAttestation = createAttestation;
|
|
13
|
+
exports.verifyAttestation = verifyAttestation;
|
|
14
|
+
exports.computeReputation = computeReputation;
|
|
15
|
+
exports.defaultReputation = defaultReputation;
|
|
12
16
|
exports.createToken = createToken;
|
|
13
17
|
exports.decodeToken = decodeToken;
|
|
14
18
|
exports.calculateScore = calculateScore;
|
|
19
|
+
exports.calculateTotalScore = calculateTotalScore;
|
|
15
20
|
exports.calculateLevel = calculateLevel;
|
|
16
21
|
const ed25519_1 = require("@noble/curves/ed25519");
|
|
17
22
|
const sha256_1 = require("@noble/hashes/sha256");
|
|
18
23
|
const utils_1 = require("@noble/curves/abstract/utils");
|
|
19
24
|
const bs58_1 = __importDefault(require("bs58"));
|
|
20
25
|
// ── DID generation ────────────────────────────────────────────────────────────
|
|
21
|
-
/**
|
|
22
|
-
* Genera un keypair Ed25519 y construye un did:key
|
|
23
|
-
* El DID es portable — mismo keypair = mismo DID en cualquier dispositivo
|
|
24
|
-
*/
|
|
25
26
|
function generateKeypair() {
|
|
26
27
|
const privateKey = ed25519_1.ed25519.utils.randomPrivateKey();
|
|
27
28
|
const publicKey = ed25519_1.ed25519.getPublicKey(privateKey);
|
|
28
29
|
const did = publicKeyToDID(publicKey);
|
|
29
30
|
return { did, publicKey, privateKey };
|
|
30
31
|
}
|
|
31
|
-
/**
|
|
32
|
-
* Reconstruye el keypair desde una llave privada guardada
|
|
33
|
-
*/
|
|
34
32
|
function keypairFromPrivateKey(privateKey) {
|
|
35
33
|
const publicKey = ed25519_1.ed25519.getPublicKey(privateKey);
|
|
36
34
|
const did = publicKeyToDID(publicKey);
|
|
37
35
|
return { did, publicKey, privateKey };
|
|
38
36
|
}
|
|
39
|
-
/**
|
|
40
|
-
* did:key format: did:key:z6Mk<base58btc(0xed01 + publicKey)>
|
|
41
|
-
* 0xed01 = multicodec prefix para Ed25519
|
|
42
|
-
*/
|
|
43
37
|
function publicKeyToDID(publicKey) {
|
|
44
38
|
const multicodec = new Uint8Array([0xed, 0x01, ...publicKey]);
|
|
45
39
|
const encoded = bs58_1.default.encode(multicodec);
|
|
46
40
|
return `did:key:z${encoded}`;
|
|
47
41
|
}
|
|
48
42
|
// ── Nullifier ─────────────────────────────────────────────────────────────────
|
|
49
|
-
/**
|
|
50
|
-
* Deriva el nullifier desde datos del documento + embedding de cara cuantizado.
|
|
51
|
-
* Determinístico: mismo documento + misma cara = mismo nullifier en cualquier dispositivo.
|
|
52
|
-
* Resistente a Sybil: misma cédula no puede generar dos nullifiers distintos
|
|
53
|
-
* si el face embedding se mantiene.
|
|
54
|
-
*
|
|
55
|
-
* @param cedulaNumber Número de cédula colombiana
|
|
56
|
-
* @param fechaNacimiento YYYY-MM-DD
|
|
57
|
-
* @param faceEmbedding Float32Array (512 dims de InsightFace), cuantizado a 2 decimales
|
|
58
|
-
*/
|
|
59
43
|
function deriveNullifier(cedulaNumber, fechaNacimiento, faceEmbedding) {
|
|
60
|
-
// Cuantizar embedding para absorber ruido entre fotos del mismo rostro
|
|
61
44
|
const quantized = quantizeEmbedding(faceEmbedding, 2);
|
|
62
|
-
// Combinar datos estables del documento con la llave biométrica
|
|
63
45
|
const input = `${cedulaNumber}:${fechaNacimiento}:${float32ToHex(quantized)}`;
|
|
64
|
-
// Hash final — 32 bytes = 256 bits de seguridad
|
|
65
46
|
const hash = (0, sha256_1.sha256)(new TextEncoder().encode(input));
|
|
66
47
|
return "0x" + (0, utils_1.bytesToHex)(hash);
|
|
67
48
|
}
|
|
68
|
-
/**
|
|
69
|
-
* Cuantiza el embedding de cara para que pequeñas variaciones
|
|
70
|
-
* entre fotos del mismo rostro produzcan el mismo resultado.
|
|
71
|
-
* precision=2 → rounds to 0.01 increments
|
|
72
|
-
*/
|
|
73
49
|
function quantizeEmbedding(embedding, precision) {
|
|
74
50
|
const factor = Math.pow(10, precision);
|
|
75
51
|
return new Float32Array(embedding.map(v => Math.round(v * factor) / factor));
|
|
76
52
|
}
|
|
77
53
|
function float32ToHex(arr) {
|
|
78
|
-
return Array.from(arr).map(v =>
|
|
79
|
-
// Representación fija de 4 decimales para consistencia cross-platform
|
|
80
|
-
return v.toFixed(2).replace("-", "n").replace(".", "p");
|
|
81
|
-
}).join(",");
|
|
54
|
+
return Array.from(arr).map(v => v.toFixed(2).replace("-", "n").replace(".", "p")).join(",");
|
|
82
55
|
}
|
|
83
56
|
// ── Firmas ────────────────────────────────────────────────────────────────────
|
|
84
|
-
/**
|
|
85
|
-
* Firma un payload con la llave privada del DID
|
|
86
|
-
*/
|
|
87
57
|
function sign(payload, privateKey) {
|
|
88
58
|
const msg = (0, sha256_1.sha256)(new TextEncoder().encode(JSON.stringify(payload)));
|
|
89
59
|
const sig = ed25519_1.ed25519.sign(msg, privateKey);
|
|
90
60
|
return (0, utils_1.bytesToHex)(sig);
|
|
91
61
|
}
|
|
92
|
-
/**
|
|
93
|
-
* Verifica una firma contra un DID did:key:...
|
|
94
|
-
*/
|
|
95
62
|
function verify(payload, signature, did) {
|
|
96
63
|
try {
|
|
97
64
|
const publicKey = didToPublicKey(did);
|
|
@@ -107,19 +74,73 @@ function didToPublicKey(did) {
|
|
|
107
74
|
throw new Error("Solo did:key:z... soportado");
|
|
108
75
|
const encoded = did.replace("did:key:z", "");
|
|
109
76
|
const multicodec = bs58_1.default.decode(encoded);
|
|
110
|
-
return multicodec.slice(2);
|
|
77
|
+
return multicodec.slice(2);
|
|
78
|
+
}
|
|
79
|
+
// ── Bot Reputation — Attestations ─────────────────────────────────────────────
|
|
80
|
+
/**
|
|
81
|
+
* Crea una attestation firmada desde un servicio verificado hacia un bot.
|
|
82
|
+
*
|
|
83
|
+
* @param serviceKeypair - Keypair del servicio que emite (ej: MCP de Uber)
|
|
84
|
+
* @param targetDid - DID del bot que recibe la calificación
|
|
85
|
+
* @param value - +1 buen comportamiento, -1 mal comportamiento
|
|
86
|
+
* @param context - Descriptor: "payment-completed", "spam-detected", etc.
|
|
87
|
+
*/
|
|
88
|
+
function createAttestation(serviceKeypair, targetDid, value, context) {
|
|
89
|
+
const payload = {
|
|
90
|
+
issuer_did: serviceKeypair.did,
|
|
91
|
+
target_did: targetDid,
|
|
92
|
+
value,
|
|
93
|
+
context,
|
|
94
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
95
|
+
};
|
|
96
|
+
const sig = sign(payload, serviceKeypair.privateKey);
|
|
97
|
+
return { ...payload, sig };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Verifica la firma de una attestation.
|
|
101
|
+
* Retorna false si la firma no coincide con el issuer_did.
|
|
102
|
+
*/
|
|
103
|
+
function verifyAttestation(att) {
|
|
104
|
+
const { sig, ...payload } = att;
|
|
105
|
+
return verify(payload, sig, att.issuer_did);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Aplica una lista de attestations verificadas a una reputación base.
|
|
109
|
+
* El score se clampea a [0, 20] y empieza en 10 (neutral).
|
|
110
|
+
*
|
|
111
|
+
* Solo attestations con firma válida son consideradas.
|
|
112
|
+
*
|
|
113
|
+
* @param attestations - Lista de attestations recibidas
|
|
114
|
+
* @param base - Score inicial (default: 10)
|
|
115
|
+
*/
|
|
116
|
+
function computeReputation(attestations, base = 10) {
|
|
117
|
+
const valid = attestations.filter(verifyAttestation);
|
|
118
|
+
const delta = valid.reduce((sum, a) => sum + a.value, 0);
|
|
119
|
+
const score = Math.max(0, Math.min(20, base + delta));
|
|
120
|
+
return {
|
|
121
|
+
score,
|
|
122
|
+
attestations: valid.length,
|
|
123
|
+
last_updated: Math.floor(Date.now() / 1000),
|
|
124
|
+
};
|
|
111
125
|
}
|
|
112
|
-
// ── SPT Token ─────────────────────────────────────────────────────────────────
|
|
113
126
|
/**
|
|
114
|
-
*
|
|
115
|
-
* Lifetime por defecto: 24 horas.
|
|
127
|
+
* Reputación neutral por defecto — para bots sin historial.
|
|
116
128
|
*/
|
|
129
|
+
function defaultReputation() {
|
|
130
|
+
return { score: 10, attestations: 0, last_updated: Math.floor(Date.now() / 1000) };
|
|
131
|
+
}
|
|
132
|
+
// ── SPT Token ─────────────────────────────────────────────────────────────────
|
|
117
133
|
function createToken(keypair, nullifier, credentials, options = {}) {
|
|
118
134
|
const now = Math.floor(Date.now() / 1000);
|
|
135
|
+
const identity_s = calculateScore(credentials);
|
|
136
|
+
const botRep = options.bot_rep ?? defaultReputation();
|
|
137
|
+
const total_score = Math.min(100, identity_s + botRep.score);
|
|
119
138
|
const payload = {
|
|
120
139
|
sip: "1",
|
|
121
140
|
did: keypair.did,
|
|
122
|
-
score:
|
|
141
|
+
score: total_score,
|
|
142
|
+
identity_score: identity_s,
|
|
143
|
+
bot_rep: botRep,
|
|
123
144
|
level: calculateLevel(credentials),
|
|
124
145
|
country: options.country,
|
|
125
146
|
credentials,
|
|
@@ -132,9 +153,6 @@ function createToken(keypair, nullifier, credentials, options = {}) {
|
|
|
132
153
|
const tokenData = { ...payload, sig: signature };
|
|
133
154
|
return Buffer.from(JSON.stringify(tokenData)).toString("base64url");
|
|
134
155
|
}
|
|
135
|
-
/**
|
|
136
|
-
* Decodifica y verifica un SPT. Retorna null si es inválido o expirado.
|
|
137
|
-
*/
|
|
138
156
|
function decodeToken(spt) {
|
|
139
157
|
try {
|
|
140
158
|
const raw = JSON.parse(Buffer.from(spt, "base64url").toString());
|
|
@@ -150,19 +168,30 @@ function decodeToken(spt) {
|
|
|
150
168
|
}
|
|
151
169
|
}
|
|
152
170
|
// ── Trust Score ───────────────────────────────────────────────────────────────
|
|
171
|
+
/**
|
|
172
|
+
* Puntajes de credenciales humanas — escalados a 80 puntos máximo.
|
|
173
|
+
* Los 20 puntos restantes vienen de BotReputation.
|
|
174
|
+
* Total máximo: 80 (identidad) + 20 (reputación) = 100.
|
|
175
|
+
*/
|
|
153
176
|
const CREDENTIAL_SCORES = {
|
|
154
|
-
EmailVerified: 10
|
|
155
|
-
PhoneVerified: 15
|
|
156
|
-
GitHubLinked: 20
|
|
157
|
-
DocumentVerified: 25
|
|
158
|
-
FaceMatch: 20
|
|
159
|
-
BiometricBound: 10
|
|
177
|
+
EmailVerified: 8, // antes: 10
|
|
178
|
+
PhoneVerified: 12, // antes: 15
|
|
179
|
+
GitHubLinked: 16, // antes: 20
|
|
180
|
+
DocumentVerified: 20, // antes: 25
|
|
181
|
+
FaceMatch: 16, // antes: 20
|
|
182
|
+
BiometricBound: 8, // antes: 10
|
|
183
|
+
// Total máximo: 80
|
|
160
184
|
};
|
|
161
185
|
function calculateScore(credentials) {
|
|
162
186
|
return credentials.reduce((sum, c) => sum + (CREDENTIAL_SCORES[c] ?? 0), 0);
|
|
163
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Score total = identidad (0-80) + reputación bot (0-20) = 0-100
|
|
190
|
+
*/
|
|
191
|
+
function calculateTotalScore(credentials, botRep = defaultReputation()) {
|
|
192
|
+
return Math.min(100, calculateScore(credentials) + botRep.score);
|
|
193
|
+
}
|
|
164
194
|
function calculateLevel(credentials) {
|
|
165
|
-
const score = calculateScore(credentials);
|
|
166
195
|
const has = (c) => credentials.includes(c);
|
|
167
196
|
if (has("DocumentVerified") && has("FaceMatch"))
|
|
168
197
|
return "KYCFull";
|