soulprint-core 0.1.0

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.
@@ -0,0 +1,71 @@
1
+ export interface SoulprintKeypair {
2
+ did: string;
3
+ publicKey: Uint8Array;
4
+ privateKey: Uint8Array;
5
+ }
6
+ export interface SoulprintToken {
7
+ sip: "1";
8
+ did: string;
9
+ score: number;
10
+ level: TrustLevel;
11
+ country?: string;
12
+ credentials: CredentialType[];
13
+ nullifier: string;
14
+ zkp?: string;
15
+ issued: number;
16
+ expires: number;
17
+ network_sig?: string;
18
+ }
19
+ export type TrustLevel = "Unverified" | "EmailVerified" | "PhoneVerified" | "KYCLite" | "KYCFull";
20
+ 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
+ export declare function generateKeypair(): SoulprintKeypair;
26
+ /**
27
+ * Reconstruye el keypair desde una llave privada guardada
28
+ */
29
+ 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
+ export declare function deriveNullifier(cedulaNumber: string, fechaNacimiento: string, faceEmbedding: Float32Array): string;
41
+ /**
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
45
+ */
46
+ export declare function quantizeEmbedding(embedding: Float32Array, precision: number): Float32Array;
47
+ /**
48
+ * Firma un payload con la llave privada del DID
49
+ */
50
+ export declare function sign(payload: object, privateKey: Uint8Array): string;
51
+ /**
52
+ * Verifica una firma contra un DID did:key:...
53
+ */
54
+ export declare function verify(payload: object, signature: string, did: string): boolean;
55
+ /**
56
+ * Crea un Soulprint Token (SPT) firmado — el JWT de Soulprint.
57
+ * Lifetime por defecto: 24 horas.
58
+ */
59
+ export declare function createToken(keypair: SoulprintKeypair, nullifier: string, credentials: CredentialType[], options?: {
60
+ lifetimeSeconds?: number;
61
+ country?: string;
62
+ zkProof?: string;
63
+ }): string;
64
+ /**
65
+ * Decodifica y verifica un SPT. Retorna null si es inválido o expirado.
66
+ */
67
+ export declare function decodeToken(spt: string): (SoulprintToken & {
68
+ sig: string;
69
+ }) | null;
70
+ export declare function calculateScore(credentials: CredentialType[]): number;
71
+ export declare function calculateLevel(credentials: CredentialType[]): TrustLevel;
package/dist/index.js ADDED
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateKeypair = generateKeypair;
7
+ exports.keypairFromPrivateKey = keypairFromPrivateKey;
8
+ exports.deriveNullifier = deriveNullifier;
9
+ exports.quantizeEmbedding = quantizeEmbedding;
10
+ exports.sign = sign;
11
+ exports.verify = verify;
12
+ exports.createToken = createToken;
13
+ exports.decodeToken = decodeToken;
14
+ exports.calculateScore = calculateScore;
15
+ exports.calculateLevel = calculateLevel;
16
+ const ed25519_1 = require("@noble/curves/ed25519");
17
+ const sha256_1 = require("@noble/hashes/sha256");
18
+ const utils_1 = require("@noble/curves/abstract/utils");
19
+ const bs58_1 = __importDefault(require("bs58"));
20
+ // ── 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
+ function generateKeypair() {
26
+ const privateKey = ed25519_1.ed25519.utils.randomPrivateKey();
27
+ const publicKey = ed25519_1.ed25519.getPublicKey(privateKey);
28
+ const did = publicKeyToDID(publicKey);
29
+ return { did, publicKey, privateKey };
30
+ }
31
+ /**
32
+ * Reconstruye el keypair desde una llave privada guardada
33
+ */
34
+ function keypairFromPrivateKey(privateKey) {
35
+ const publicKey = ed25519_1.ed25519.getPublicKey(privateKey);
36
+ const did = publicKeyToDID(publicKey);
37
+ return { did, publicKey, privateKey };
38
+ }
39
+ /**
40
+ * did:key format: did:key:z6Mk<base58btc(0xed01 + publicKey)>
41
+ * 0xed01 = multicodec prefix para Ed25519
42
+ */
43
+ function publicKeyToDID(publicKey) {
44
+ const multicodec = new Uint8Array([0xed, 0x01, ...publicKey]);
45
+ const encoded = bs58_1.default.encode(multicodec);
46
+ return `did:key:z${encoded}`;
47
+ }
48
+ // ── 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
+ function deriveNullifier(cedulaNumber, fechaNacimiento, faceEmbedding) {
60
+ // Cuantizar embedding para absorber ruido entre fotos del mismo rostro
61
+ const quantized = quantizeEmbedding(faceEmbedding, 2);
62
+ // Combinar datos estables del documento con la llave biométrica
63
+ const input = `${cedulaNumber}:${fechaNacimiento}:${float32ToHex(quantized)}`;
64
+ // Hash final — 32 bytes = 256 bits de seguridad
65
+ const hash = (0, sha256_1.sha256)(new TextEncoder().encode(input));
66
+ return "0x" + (0, utils_1.bytesToHex)(hash);
67
+ }
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
+ function quantizeEmbedding(embedding, precision) {
74
+ const factor = Math.pow(10, precision);
75
+ return new Float32Array(embedding.map(v => Math.round(v * factor) / factor));
76
+ }
77
+ 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(",");
82
+ }
83
+ // ── Firmas ────────────────────────────────────────────────────────────────────
84
+ /**
85
+ * Firma un payload con la llave privada del DID
86
+ */
87
+ function sign(payload, privateKey) {
88
+ const msg = (0, sha256_1.sha256)(new TextEncoder().encode(JSON.stringify(payload)));
89
+ const sig = ed25519_1.ed25519.sign(msg, privateKey);
90
+ return (0, utils_1.bytesToHex)(sig);
91
+ }
92
+ /**
93
+ * Verifica una firma contra un DID did:key:...
94
+ */
95
+ function verify(payload, signature, did) {
96
+ try {
97
+ const publicKey = didToPublicKey(did);
98
+ const msg = (0, sha256_1.sha256)(new TextEncoder().encode(JSON.stringify(payload)));
99
+ return ed25519_1.ed25519.verify((0, utils_1.hexToBytes)(signature), msg, publicKey);
100
+ }
101
+ catch {
102
+ return false;
103
+ }
104
+ }
105
+ function didToPublicKey(did) {
106
+ if (!did.startsWith("did:key:z"))
107
+ throw new Error("Solo did:key:z... soportado");
108
+ const encoded = did.replace("did:key:z", "");
109
+ const multicodec = bs58_1.default.decode(encoded);
110
+ return multicodec.slice(2); // saltar 0xed 0x01
111
+ }
112
+ // ── SPT Token ─────────────────────────────────────────────────────────────────
113
+ /**
114
+ * Crea un Soulprint Token (SPT) firmado — el JWT de Soulprint.
115
+ * Lifetime por defecto: 24 horas.
116
+ */
117
+ function createToken(keypair, nullifier, credentials, options = {}) {
118
+ const now = Math.floor(Date.now() / 1000);
119
+ const payload = {
120
+ sip: "1",
121
+ did: keypair.did,
122
+ score: calculateScore(credentials),
123
+ level: calculateLevel(credentials),
124
+ country: options.country,
125
+ credentials,
126
+ nullifier,
127
+ ...(options.zkProof ? { zkp: options.zkProof } : {}),
128
+ issued: now,
129
+ expires: now + (options.lifetimeSeconds ?? 86400),
130
+ };
131
+ const signature = sign(payload, keypair.privateKey);
132
+ const tokenData = { ...payload, sig: signature };
133
+ return Buffer.from(JSON.stringify(tokenData)).toString("base64url");
134
+ }
135
+ /**
136
+ * Decodifica y verifica un SPT. Retorna null si es inválido o expirado.
137
+ */
138
+ function decodeToken(spt) {
139
+ try {
140
+ const raw = JSON.parse(Buffer.from(spt, "base64url").toString());
141
+ const { sig, ...payload } = raw;
142
+ if (!verify(payload, sig, payload.did))
143
+ return null;
144
+ if (payload.expires < Math.floor(Date.now() / 1000))
145
+ return null;
146
+ return raw;
147
+ }
148
+ catch {
149
+ return null;
150
+ }
151
+ }
152
+ // ── Trust Score ───────────────────────────────────────────────────────────────
153
+ const CREDENTIAL_SCORES = {
154
+ EmailVerified: 10,
155
+ PhoneVerified: 15,
156
+ GitHubLinked: 20,
157
+ DocumentVerified: 25,
158
+ FaceMatch: 20,
159
+ BiometricBound: 10,
160
+ };
161
+ function calculateScore(credentials) {
162
+ return credentials.reduce((sum, c) => sum + (CREDENTIAL_SCORES[c] ?? 0), 0);
163
+ }
164
+ function calculateLevel(credentials) {
165
+ const score = calculateScore(credentials);
166
+ const has = (c) => credentials.includes(c);
167
+ if (has("DocumentVerified") && has("FaceMatch"))
168
+ return "KYCFull";
169
+ if (has("DocumentVerified") || has("FaceMatch"))
170
+ return "KYCLite";
171
+ if (has("PhoneVerified"))
172
+ return "PhoneVerified";
173
+ if (has("EmailVerified"))
174
+ return "EmailVerified";
175
+ return "Unverified";
176
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "soulprint-core",
3
+ "version": "0.1.0",
4
+ "description": "Soulprint core — DID keypairs, SPT tokens, Ed25519 signatures, trust scoring",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "README.md"
10
+ ],
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/manuelariasfz/soulprint",
17
+ "directory": "packages/core"
18
+ },
19
+ "homepage": "https://github.com/manuelariasfz/soulprint#readme",
20
+ "keywords": [
21
+ "soulprint",
22
+ "kyc",
23
+ "did",
24
+ "decentralized-identity",
25
+ "zero-knowledge",
26
+ "ai-agents"
27
+ ],
28
+ "license": "MIT",
29
+ "dependencies": {
30
+ "@noble/curves": "^1.4.0",
31
+ "@noble/hashes": "^1.4.0",
32
+ "bs58": "^6.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "typescript": "^5.4.0",
36
+ "@types/node": "^20.0.0"
37
+ },
38
+ "engines": {
39
+ "node": ">=18.0.0"
40
+ },
41
+ "scripts": {
42
+ "build": "tsc"
43
+ }
44
+ }