ns-auth-sdk 1.12.2 → 1.12.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/dist/index.mjs CHANGED
@@ -1,5 +1,9 @@
1
1
  import { finalizeEvent, getPublicKey } from "applesauce-core/helpers";
2
2
  import * as secp256k1 from "@noble/secp256k1";
3
+ import { sha256 } from "@noble/hashes/sha2";
4
+ import { pbkdf2 } from "@noble/hashes/pbkdf2";
5
+ import { aesGcm } from "@noble/ciphers/aes";
6
+ import { randomBytes } from "@noble/secp256k1/utils";
3
7
 
4
8
  export * from "applesauce-core"
5
9
 
@@ -171,8 +175,6 @@ const PRF_EVAL_INPUT = new TextEncoder().encode("nostr-pwk");
171
175
  */
172
176
  async function isPrfSupported() {
173
177
  try {
174
- const cryptoObj = typeof window !== "undefined" && window.crypto || globalThis.crypto;
175
- if (!cryptoObj || !cryptoObj.subtle) return false;
176
178
  const response = await navigator.credentials.get({ publicKey: {
177
179
  challenge: crypto.getRandomValues(new Uint8Array(32)),
178
180
  allowCredentials: [],
@@ -256,32 +258,29 @@ async function getPrfSecret(credentialId, options) {
256
258
  * password-protected private key. The private key is wrapped with a password-
257
259
  * derived AES-GCM key and stored alongside the public key (SPKI).
258
260
  *
259
- * This is a minimal, browser-oriented implementation designed to provide a
260
- * practical alternative while keeping crypto surface area focused and safe.
261
+ * This implementation uses @noble libraries for cryptographic operations,
262
+ * ensuring functionality even when Web Crypto API is unavailable.
261
263
  */
262
264
  function toBase64(bytes) {
263
- const arr = new Uint8Array(bytes);
264
265
  let binary = "";
265
- for (let i = 0; i < arr.byteLength; i++) binary += String.fromCharCode(arr[i]);
266
+ for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
266
267
  if (typeof window !== "undefined" && window.btoa) return window.btoa(binary);
267
- return Buffer.from(binary, "binary").toString("base64");
268
+ return btoa(binary);
268
269
  }
269
270
  function fromBase64(b64) {
270
271
  if (typeof window !== "undefined" && window.atob) {
271
- const bin = window.atob(b64);
272
- const bytes = new Uint8Array(bin.length);
273
- for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
274
- return bytes;
275
- }
276
- return new Uint8Array(Buffer.from(b64, "base64"));
272
+ const bin$1 = window.atob(b64);
273
+ const bytes$1 = new Uint8Array(bin$1.length);
274
+ for (let i = 0; i < bin$1.length; i++) bytes$1[i] = bin$1.charCodeAt(i);
275
+ return bytes$1;
276
+ }
277
+ const bin = atob(b64);
278
+ const bytes = new Uint8Array(bin.length);
279
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
280
+ return bytes;
277
281
  }
278
282
  async function checkPRFSupport() {
279
283
  try {
280
- const cryptoObj = typeof window !== "undefined" && window.crypto || globalThis.crypto;
281
- if (!cryptoObj || !cryptoObj.subtle) {
282
- console.log("Web Crypto API not available, falling back to password-protected key");
283
- return false;
284
- }
285
284
  const supported = (typeof PublicKeyCredential !== "undefined" ? await PublicKeyCredential.getClientCapabilities() : null)?.["extension:prf"] === true;
286
285
  if (supported) console.log("PRF extension is supported.");
287
286
  else console.log("PRF extension is not supported.");
@@ -292,107 +291,43 @@ async function checkPRFSupport() {
292
291
  }
293
292
  }
294
293
  async function generatePasswordProtectedKey(password) {
295
- const cryptoObj = typeof window !== "undefined" && window.crypto || globalThis.crypto;
296
- if (!cryptoObj || !cryptoObj.subtle) throw new Error("Web Crypto API not available");
297
- const kp = await cryptoObj.subtle.generateKey({
298
- name: "ECDSA",
299
- namedCurve: "P-256"
300
- }, true, ["sign"]);
301
- const publicKey = kp.publicKey;
302
- const privateKey = kp.privateKey;
303
- const publicKeySpkiBase64 = toBase64(await cryptoObj.subtle.exportKey("spki", publicKey));
304
- const privateKeyJwk = await cryptoObj.subtle.exportKey("jwk", privateKey);
305
- const jwkStr = JSON.stringify(privateKeyJwk);
306
- const salt = cryptoObj.getRandomValues(new Uint8Array(16));
307
- const pbKey = await cryptoObj.subtle.importKey("raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveKey"]);
308
- const aesKey = await cryptoObj.subtle.deriveKey({
309
- name: "PBKDF2",
310
- salt: salt.slice().buffer,
311
- iterations: 1e5,
312
- hash: "SHA-256"
313
- }, pbKey, {
314
- name: "AES-GCM",
315
- length: 256
316
- }, false, ["encrypt"]);
317
- const iv = cryptoObj.getRandomValues(new Uint8Array(12));
318
- const enc = new TextEncoder().encode(jwkStr);
294
+ const privateKey = secp256k1.utils.randomPrivateKey();
295
+ const publicKeySpkiBase64 = toBase64(secp256k1.getPublicKey(privateKey, true));
296
+ const salt = randomBytes(16);
297
+ const derivedKey = pbkdf2(sha256, new TextEncoder().encode(password), salt, {
298
+ c: 1e5,
299
+ dkLen: 32
300
+ });
301
+ const iv = randomBytes(12);
319
302
  return {
320
303
  publicKeySpkiBase64,
321
- wrappedPrivateKeyBase64: toBase64(await cryptoObj.subtle.encrypt({
322
- name: "AES-GCM",
323
- iv
324
- }, aesKey, enc)),
325
- saltBase64: toBase64(salt.buffer),
326
- ivBase64: toBase64(iv.buffer)
304
+ wrappedPrivateKeyBase64: toBase64(aesGcm(derivedKey, iv).encrypt(privateKey)),
305
+ saltBase64: toBase64(salt),
306
+ ivBase64: toBase64(iv)
327
307
  };
328
308
  }
329
309
  async function unwrapPasswordProtectedPrivateKey(bundle, password) {
330
- const cryptoObj = typeof window !== "undefined" && window.crypto || globalThis.crypto;
331
- if (!cryptoObj || !cryptoObj.subtle) throw new Error("Web Crypto API not available");
332
310
  const salt = fromBase64(bundle.saltBase64);
333
- const pbKey = await cryptoObj.subtle.importKey("raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveKey"]);
334
- const aesKey = await cryptoObj.subtle.deriveKey({
335
- name: "PBKDF2",
336
- salt: salt.slice().buffer,
337
- iterations: 1e5,
338
- hash: "SHA-256"
339
- }, pbKey, {
340
- name: "AES-GCM",
341
- length: 256
342
- }, false, ["decrypt"]);
311
+ const derivedKey = pbkdf2(sha256, new TextEncoder().encode(password), salt, {
312
+ c: 1e5,
313
+ dkLen: 32
314
+ });
343
315
  const iv = fromBase64(bundle.ivBase64);
344
316
  const ct = fromBase64(bundle.wrappedPrivateKeyBase64);
345
- const jwkBytes = await cryptoObj.subtle.decrypt({
346
- name: "AES-GCM",
347
- iv: iv.slice()
348
- }, aesKey, ct.slice());
349
- const jwkStr = new TextDecoder().decode(jwkBytes);
350
- const privateKeyJwk = JSON.parse(jwkStr);
351
- await cryptoObj.subtle.importKey("jwk", privateKeyJwk, {
352
- name: "ECDSA",
353
- namedCurve: "P-256"
354
- }, true, ["sign"]);
355
- const d = privateKeyJwk.d;
356
- if (!d) throw new Error("Missing private scalar in JWK");
357
- return base64UrlToBytes(d);
358
- }
359
- function base64UrlToBytes(base64url) {
360
- let base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
361
- const pad = base64.length % 4;
362
- if (pad) base64 += "=".repeat(4 - pad);
363
- if (typeof window !== "undefined" && window.atob) {
364
- const binary = window.atob(base64);
365
- const bytes = new Uint8Array(binary.length);
366
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
367
- return bytes;
368
- }
369
- return new Uint8Array(Buffer.from(base64, "base64"));
317
+ return aesGcm(derivedKey, iv).decrypt(ct);
370
318
  }
371
- async function importPublicKeyFromBundle(bundle) {
372
- const cryptoObj = typeof window !== "undefined" && window.crypto || globalThis.crypto;
373
- if (!cryptoObj || !cryptoObj.subtle) throw new Error("Web Crypto API not available");
374
- const spki = fromBase64(bundle.publicKeySpkiBase64);
375
- return await cryptoObj.subtle.importKey("spki", spki, {
376
- name: "ECDSA",
377
- namedCurve: "P-256"
378
- }, true, ["verify"]);
319
+ function importPublicKeyFromBundle(bundle) {
320
+ return fromBase64(bundle.publicKeySpkiBase64);
379
321
  }
380
322
  const DEFAULT_SALT = "nostr-key-derivation";
381
- async function deriveNostrPrivateKey(password, salt = DEFAULT_SALT) {
382
- const cryptoObj = typeof window !== "undefined" && window.crypto || globalThis.crypto;
383
- if (!cryptoObj || !cryptoObj.subtle) throw new Error("Web Crypto API not available");
384
- const encoder = new TextEncoder();
385
- const passwordKey = await cryptoObj.subtle.importKey("raw", encoder.encode(password), "PBKDF2", false, ["deriveBits"]);
386
- const derivedBits = await cryptoObj.subtle.deriveBits({
387
- name: "PBKDF2",
388
- salt: encoder.encode(salt),
389
- iterations: 1e5,
390
- hash: "SHA-256"
391
- }, passwordKey, 256);
392
- return new Uint8Array(derivedBits);
323
+ function deriveNostrPrivateKey(password, salt = DEFAULT_SALT) {
324
+ return pbkdf2(sha256, new TextEncoder().encode(password), new TextEncoder().encode(salt), {
325
+ c: 1e5,
326
+ dkLen: 32
327
+ });
393
328
  }
394
- async function getPublicKeyFromPassword(password, salt = DEFAULT_SALT) {
395
- return getPublicKey(await deriveNostrPrivateKey(password, salt));
329
+ function getPublicKeyFromPassword(password, salt = DEFAULT_SALT) {
330
+ return getPublicKey(deriveNostrPrivateKey(password, salt));
396
331
  }
397
332
 
398
333
  //#endregion
@@ -439,7 +374,7 @@ async function verifyRecoverySignature(kind0) {
439
374
  if (!parseRecoveryTag(kind0.tags || [])) return false;
440
375
  const signature = getRecoverySignature(kind0);
441
376
  if (!signature || !kind0.pubkey) return false;
442
- const messageHash = await sha256(kind0.pubkey);
377
+ const messageHash = await sha256$1(kind0.pubkey);
443
378
  const signatureBytes = hexToBytes(signature);
444
379
  const pubkeyBytes = hexToBytes(kind0.pubkey);
445
380
  return secp256k1.verify(signatureBytes, messageHash, pubkeyBytes);
@@ -447,7 +382,7 @@ async function verifyRecoverySignature(kind0) {
447
382
  return false;
448
383
  }
449
384
  }
450
- async function sha256(message) {
385
+ async function sha256$1(message) {
451
386
  const msgBuffer = new TextEncoder().encode(message);
452
387
  const subtle = globalThis.crypto?.subtle;
453
388
  if (!subtle) throw new Error("Web Crypto API not available");
@@ -675,7 +610,7 @@ var NosskeyManager = class {
675
610
  */
676
611
  async createPasswordProtectedNostrKey(password, options = {}) {
677
612
  const salt = await deriveSaltFromUsername(options.username);
678
- const pubkey = await getPublicKeyFromPassword(password, salt);
613
+ const pubkey = getPublicKeyFromPassword(password, salt);
679
614
  return {
680
615
  credentialId: bytesToHex((typeof window !== "undefined" ? window.crypto : globalThis.crypto).getRandomValues(new Uint8Array(16))),
681
616
  pubkey,
@@ -696,7 +631,7 @@ var NosskeyManager = class {
696
631
  if (isPasswordDerived) {
697
632
  if (shouldUseCache) sk = this.#keyCache.getKey(keyInfo.credentialId);
698
633
  if (!sk && password) {
699
- sk = await deriveNostrPrivateKey(password, keyInfo.salt);
634
+ sk = deriveNostrPrivateKey(password, keyInfo.salt);
700
635
  if (shouldUseCache) this.#keyCache.setKey(keyInfo.credentialId, sk);
701
636
  }
702
637
  if (!sk) throw new Error("Password required - key not in cache. Provide password to sign.");
@@ -737,7 +672,7 @@ var NosskeyManager = class {
737
672
  }
738
673
  if (!keyInfo.passwordProtectedBundle && keyInfo.salt) {
739
674
  if (!options.password) throw new Error("Password is required for password-derived keys");
740
- return bytesToHex(await deriveNostrPrivateKey(options.password, keyInfo.salt));
675
+ return bytesToHex(deriveNostrPrivateKey(options.password, keyInfo.salt));
741
676
  }
742
677
  let usedCredentialId = credentialId;
743
678
  if (!usedCredentialId && keyInfo.credentialId) usedCredentialId = hexToBytes(keyInfo.credentialId);
@@ -762,8 +697,8 @@ var NosskeyManager = class {
762
697
  if (keyInfo.recovery) throw new Error("Recovery already configured");
763
698
  if (!(currentCredentialId || (keyInfo.credentialId ? hexToBytes(keyInfo.credentialId) : void 0))) throw new Error("Credential ID required");
764
699
  const recoverySalt = bytesToHex((typeof window !== "undefined" ? window.crypto : globalThis.crypto).getRandomValues(new Uint8Array(16)));
765
- const recoveryPubkey = await getPublicKeyFromPassword(password, recoverySalt);
766
- const recoverySk = await deriveNostrPrivateKey(password, recoverySalt);
700
+ const recoveryPubkey = getPublicKeyFromPassword(password, recoverySalt);
701
+ const recoverySk = deriveNostrPrivateKey(password, recoverySalt);
767
702
  const signature = this.#signWithKey(recoverySk, keyInfo.pubkey);
768
703
  this.#clearKey(recoverySk);
769
704
  const updatedKeyInfo = {
@@ -798,13 +733,13 @@ var NosskeyManager = class {
798
733
  if (!keyInfo.recovery) throw new Error("No recovery key configured");
799
734
  if (!newCredentialId) throw new Error("New credential ID is required for recovery");
800
735
  const { recoveryPubkey, recoverySalt } = keyInfo.recovery;
801
- if (await getPublicKeyFromPassword(password, recoverySalt) !== recoveryPubkey) throw new Error("Invalid recovery password");
736
+ if (getPublicKeyFromPassword(password, recoverySalt) !== recoveryPubkey) throw new Error("Invalid recovery password");
802
737
  const { secret: newSk } = await getPrfSecret(newCredentialId, this.#prfOptions);
803
738
  const newPubkey = getPublicKey(newSk);
804
739
  this.#clearKey(newSk);
805
740
  const newRecoverySalt = bytesToHex((typeof window !== "undefined" ? window.crypto : globalThis.crypto).getRandomValues(new Uint8Array(16)));
806
- const newRecoveryPubkey = await getPublicKeyFromPassword(password, newRecoverySalt);
807
- const recoverySk = await deriveNostrPrivateKey(password, recoverySalt);
741
+ const newRecoveryPubkey = getPublicKeyFromPassword(password, newRecoverySalt);
742
+ const recoverySk = deriveNostrPrivateKey(password, recoverySalt);
808
743
  const signature = this.#signWithKey(recoverySk, newPubkey);
809
744
  this.#clearKey(recoverySk);
810
745
  const updatedKeyInfo = {