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 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
- * Cuantiza el embedding de cara para que pequeñas variaciones
43
- * entre fotos del mismo rostro produzcan el mismo resultado.
44
- * precision=2 rounds to 0.01 increments
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 quantizeEmbedding(embedding: Float32Array, precision: number): Float32Array;
68
+ export declare function createAttestation(serviceKeypair: SoulprintKeypair, targetDid: string, value: 1 | -1, context: string): BotAttestation;
47
69
  /**
48
- * Firma un payload con la llave privada del DID
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 sign(payload: object, privateKey: Uint8Array): string;
73
+ export declare function verifyAttestation(att: BotAttestation): boolean;
51
74
  /**
52
- * Verifica una firma contra un DID did:key:...
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 verify(payload: object, signature: string, did: string): boolean;
83
+ export declare function computeReputation(attestations: BotAttestation[], base?: number): BotReputation;
55
84
  /**
56
- * Crea un Soulprint Token (SPT) firmado el JWT de Soulprint.
57
- * Lifetime por defecto: 24 horas.
85
+ * Reputación neutral por defectopara 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); // saltar 0xed 0x01
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
- * Crea un Soulprint Token (SPT) firmado el JWT de Soulprint.
115
- * Lifetime por defecto: 24 horas.
127
+ * Reputación neutral por defectopara 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: calculateScore(credentials),
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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "soulprint-core",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Soulprint core — DID keypairs, SPT tokens, Ed25519 signatures, trust scoring",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",