@thurinlabs/identity-kit 1.0.2 → 1.0.4
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/LICENSE +21 -0
- package/README.md +4 -0
- package/dist/embed.global.js +45 -22
- package/dist/embed.global.js.map +1 -1
- package/dist/index.cjs +45 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +45 -22
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thurin Labs LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -264,6 +264,10 @@ The card talks directly to Ethereum and each proof platform — no intermediary,
|
|
|
264
264
|
| Codeberg | Repository description |
|
|
265
265
|
| Mastodon | Profile metadata |
|
|
266
266
|
|
|
267
|
+
## How a claim is verified
|
|
268
|
+
|
|
269
|
+
`verifyAttestation` checks three things: the stored key's fingerprint is the one on the claim; the stored clearsigned statement was made by that key (or one of its bound signing subkeys) and has not been altered; and the statement names the claim's address. Key validity is judged **now**, the way gpg does it: the key must currently be bound, unrevoked, and unexpired. It is deliberately not judged at the instant the signature was made, which is openpgp.js's default. That default rejects a perfectly good claim whenever the key's newest self-certification postdates the attest signature, which is exactly what happens when you add a proof after attesting and export with `export-minimal` (1.0.3).
|
|
270
|
+
|
|
267
271
|
## Key algorithms
|
|
268
272
|
|
|
269
273
|
Any curve openpgp.js can compute is accepted: Ed25519, Cv25519, NIST P-256/384/521, brainpool, RSA, and **secp256k1**. openpgp.js rejects secp256k1 by default because RFC 9580 does not list it; identity-kit turns that rejection off, since it is a compatibility rule, not a security one. A secp256k1 PGP key doubles as an Ethereum key (the address is derived from the same public point), so anything that can sign with the PGP key can sign Ethereum transactions: hold such a key if you like, but do not fund its derived address. In Node, openpgp.js needs the `eckey-utils` package for this curve; identity-kit depends on it, so `npm install` brings it in. The browser build needs nothing extra.
|
package/dist/embed.global.js
CHANGED
|
@@ -31148,7 +31148,9 @@ ${prettyStateOverride(stateOverride)}`;
|
|
|
31148
31148
|
}
|
|
31149
31149
|
|
|
31150
31150
|
// src/core/pgp.ts
|
|
31151
|
-
|
|
31151
|
+
function pgpConfig(openpgp) {
|
|
31152
|
+
return { ...openpgp.config, rejectCurves: /* @__PURE__ */ new Set() };
|
|
31153
|
+
}
|
|
31152
31154
|
function algorithmName(keyOrSubkey) {
|
|
31153
31155
|
try {
|
|
31154
31156
|
const info = keyOrSubkey.getAlgorithmInfo();
|
|
@@ -31170,7 +31172,9 @@ ${prettyStateOverride(stateOverride)}`;
|
|
|
31170
31172
|
}
|
|
31171
31173
|
async function parsePgpKey(armoredKey) {
|
|
31172
31174
|
try {
|
|
31173
|
-
const
|
|
31175
|
+
const openpgp = await Promise.resolve().then(() => (init_openpgp_min(), openpgp_min_exports));
|
|
31176
|
+
const { readKey } = openpgp;
|
|
31177
|
+
const config = pgpConfig(openpgp);
|
|
31174
31178
|
const key = await readKey({ armoredKey });
|
|
31175
31179
|
const fingerprint = key.getFingerprint().toUpperCase();
|
|
31176
31180
|
const algorithm = algorithmName(key);
|
|
@@ -31191,7 +31195,10 @@ ${prettyStateOverride(stateOverride)}`;
|
|
|
31191
31195
|
cert.signatureType,
|
|
31192
31196
|
{ userID: user.userID, key: key.keyPacket },
|
|
31193
31197
|
void 0,
|
|
31194
|
-
|
|
31198
|
+
// date: now
|
|
31199
|
+
void 0,
|
|
31200
|
+
// detached
|
|
31201
|
+
config
|
|
31195
31202
|
);
|
|
31196
31203
|
} catch {
|
|
31197
31204
|
continue;
|
|
@@ -31235,15 +31242,28 @@ ${prettyStateOverride(stateOverride)}`;
|
|
|
31235
31242
|
if (!pgpPublicKey || !pgpSignature) {
|
|
31236
31243
|
return { verified: false, reason: "Missing PGP data" };
|
|
31237
31244
|
}
|
|
31238
|
-
const
|
|
31245
|
+
const openpgp = await Promise.resolve().then(() => (init_openpgp_min(), openpgp_min_exports));
|
|
31246
|
+
const { readKey, readCleartextMessage, LiteralDataPacket } = openpgp;
|
|
31247
|
+
const config = pgpConfig(openpgp);
|
|
31239
31248
|
const publicKey = await readKey({ armoredKey: pgpPublicKey });
|
|
31240
31249
|
const keyFingerprint = publicKey.getFingerprint().toUpperCase();
|
|
31241
31250
|
if (keyFingerprint !== fingerprint.toUpperCase()) {
|
|
31242
31251
|
return { verified: false, reason: "Key fingerprint mismatch" };
|
|
31243
31252
|
}
|
|
31244
31253
|
const message = await readCleartextMessage({ cleartextMessage: pgpSignature });
|
|
31245
|
-
const
|
|
31246
|
-
|
|
31254
|
+
const msg = message;
|
|
31255
|
+
const now = /* @__PURE__ */ new Date();
|
|
31256
|
+
let signed2 = false;
|
|
31257
|
+
for (const sig of msg.signature.packets) {
|
|
31258
|
+
if (!publicKey.getKeys(sig.issuerKeyID).length) continue;
|
|
31259
|
+
const signingKey = await publicKey.getSigningKey(sig.issuerKeyID, now, void 0, config);
|
|
31260
|
+
const literal = new LiteralDataPacket();
|
|
31261
|
+
literal.setText(msg.text ?? message.getText().replace(/\r?\n/g, "\r\n"));
|
|
31262
|
+
await sig.verify(signingKey.keyPacket, sig.signatureType, literal, now, true, config);
|
|
31263
|
+
signed2 = true;
|
|
31264
|
+
break;
|
|
31265
|
+
}
|
|
31266
|
+
if (!signed2) return { verified: false, reason: "Message is not signed by this key" };
|
|
31247
31267
|
const signedText = message.getText();
|
|
31248
31268
|
if (!signedText.toLowerCase().includes(ethAddress.toLowerCase())) {
|
|
31249
31269
|
return { verified: false, reason: "Signed message does not contain ETH address" };
|
|
@@ -31333,25 +31353,28 @@ ${prettyStateOverride(stateOverride)}`;
|
|
|
31333
31353
|
var EFP_API = "https://api.ethfollow.xyz/api/v1";
|
|
31334
31354
|
async function fetchEFPGraph(address) {
|
|
31335
31355
|
try {
|
|
31336
|
-
const listsResp = await
|
|
31337
|
-
if (!listsResp.ok) return null;
|
|
31338
|
-
const listsData = await listsResp.json();
|
|
31339
|
-
const listId = listsData.primary_list;
|
|
31340
|
-
if (!listId) return null;
|
|
31341
|
-
const [followingResp, statsResp, top8Resp] = await Promise.all([
|
|
31342
|
-
fetch(`${EFP_API}/lists/${listId}/following?limit=100`),
|
|
31356
|
+
const [statsResp, listsResp] = await Promise.all([
|
|
31343
31357
|
fetch(`${EFP_API}/users/${address}/stats`),
|
|
31344
|
-
fetch(`${EFP_API}/
|
|
31358
|
+
fetch(`${EFP_API}/users/${address}/lists`)
|
|
31345
31359
|
]);
|
|
31346
|
-
|
|
31360
|
+
if (!statsResp.ok && !listsResp.ok) return null;
|
|
31347
31361
|
const statsData = statsResp.ok ? await statsResp.json() : {};
|
|
31348
|
-
const
|
|
31349
|
-
|
|
31350
|
-
|
|
31351
|
-
|
|
31352
|
-
|
|
31353
|
-
|
|
31354
|
-
|
|
31362
|
+
const listsData = listsResp.ok ? await listsResp.json() : {};
|
|
31363
|
+
const followers = parseInt(statsData.followers_count) || 0;
|
|
31364
|
+
const listId = listsData.primary_list;
|
|
31365
|
+
let following = 0;
|
|
31366
|
+
let top8 = [];
|
|
31367
|
+
if (listId) {
|
|
31368
|
+
const [followingResp, top8Resp] = await Promise.all([
|
|
31369
|
+
fetch(`${EFP_API}/lists/${listId}/following?limit=100`),
|
|
31370
|
+
fetch(`${EFP_API}/lists/${listId}/following?limit=8&tags=top8`)
|
|
31371
|
+
]);
|
|
31372
|
+
const followingData = followingResp.ok ? await followingResp.json() : { following: [] };
|
|
31373
|
+
const top8Data = top8Resp.ok ? await top8Resp.json() : { following: [] };
|
|
31374
|
+
following = (followingData.following || []).length;
|
|
31375
|
+
top8 = (top8Data.following || []).map((f2) => f2.data);
|
|
31376
|
+
}
|
|
31377
|
+
return { followers, following, top8, hasEfp: !!listId || followers > 0 };
|
|
31355
31378
|
} catch {
|
|
31356
31379
|
return null;
|
|
31357
31380
|
}
|