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.
- package/dist/index.d.ts +71 -0
- package/dist/index.js +176 -0
- package/package.json +44 -0
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|