@thurinlabs/identity-kit 1.0.1 → 1.0.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/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,14 @@ 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
+
271
+ ## Key algorithms
272
+
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.
274
+
267
275
  ## Migrating from 0.9.x
268
276
 
269
277
  1.0.0 reads the **PGPRegistry v2** contract. The v1 registry is no longer read.
@@ -31148,6 +31148,9 @@ ${prettyStateOverride(stateOverride)}`;
31148
31148
  }
31149
31149
 
31150
31150
  // src/core/pgp.ts
31151
+ function pgpConfig(openpgp) {
31152
+ return { ...openpgp.config, rejectCurves: /* @__PURE__ */ new Set() };
31153
+ }
31151
31154
  function algorithmName(keyOrSubkey) {
31152
31155
  try {
31153
31156
  const info = keyOrSubkey.getAlgorithmInfo();
@@ -31169,7 +31172,9 @@ ${prettyStateOverride(stateOverride)}`;
31169
31172
  }
31170
31173
  async function parsePgpKey(armoredKey) {
31171
31174
  try {
31172
- const { readKey } = await Promise.resolve().then(() => (init_openpgp_min(), openpgp_min_exports));
31175
+ const openpgp = await Promise.resolve().then(() => (init_openpgp_min(), openpgp_min_exports));
31176
+ const { readKey } = openpgp;
31177
+ const config = pgpConfig(openpgp);
31173
31178
  const key = await readKey({ armoredKey });
31174
31179
  const fingerprint = key.getFingerprint().toUpperCase();
31175
31180
  const algorithm = algorithmName(key);
@@ -31188,7 +31193,12 @@ ${prettyStateOverride(stateOverride)}`;
31188
31193
  await cert.verify(
31189
31194
  key.keyPacket,
31190
31195
  cert.signatureType,
31191
- { userID: user.userID, key: key.keyPacket }
31196
+ { userID: user.userID, key: key.keyPacket },
31197
+ void 0,
31198
+ // date: now
31199
+ void 0,
31200
+ // detached
31201
+ config
31192
31202
  );
31193
31203
  } catch {
31194
31204
  continue;
@@ -31232,15 +31242,28 @@ ${prettyStateOverride(stateOverride)}`;
31232
31242
  if (!pgpPublicKey || !pgpSignature) {
31233
31243
  return { verified: false, reason: "Missing PGP data" };
31234
31244
  }
31235
- const { readKey, readCleartextMessage, verify } = await Promise.resolve().then(() => (init_openpgp_min(), openpgp_min_exports));
31245
+ const openpgp = await Promise.resolve().then(() => (init_openpgp_min(), openpgp_min_exports));
31246
+ const { readKey, readCleartextMessage, LiteralDataPacket } = openpgp;
31247
+ const config = pgpConfig(openpgp);
31236
31248
  const publicKey = await readKey({ armoredKey: pgpPublicKey });
31237
31249
  const keyFingerprint = publicKey.getFingerprint().toUpperCase();
31238
31250
  if (keyFingerprint !== fingerprint.toUpperCase()) {
31239
31251
  return { verified: false, reason: "Key fingerprint mismatch" };
31240
31252
  }
31241
31253
  const message = await readCleartextMessage({ cleartextMessage: pgpSignature });
31242
- const { signatures } = await verify({ message, verificationKeys: publicKey });
31243
- await signatures[0].verified;
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" };
31244
31267
  const signedText = message.getText();
31245
31268
  if (!signedText.toLowerCase().includes(ethAddress.toLowerCase())) {
31246
31269
  return { verified: false, reason: "Signed message does not contain ETH address" };