ns-auth-sdk 1.12.3 → 1.12.5

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.cjs CHANGED
@@ -28,6 +28,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  let applesauce_core_helpers = require("applesauce-core/helpers");
29
29
  let _noble_secp256k1 = require("@noble/secp256k1");
30
30
  _noble_secp256k1 = __toESM(_noble_secp256k1);
31
+ let _noble_hashes = require("@noble/hashes");
32
+ let _noble_ciphers = require("@noble/ciphers");
33
+ let _noble_secp256k1_utils = require("@noble/secp256k1/utils");
31
34
 
32
35
  //#region src/utils/utils.ts
33
36
  /**
@@ -197,8 +200,6 @@ const PRF_EVAL_INPUT = new TextEncoder().encode("nostr-pwk");
197
200
  */
198
201
  async function isPrfSupported() {
199
202
  try {
200
- const cryptoObj = typeof window !== "undefined" && window.crypto || globalThis.crypto;
201
- if (!cryptoObj || !cryptoObj.subtle) return false;
202
203
  const response = await navigator.credentials.get({ publicKey: {
203
204
  challenge: crypto.getRandomValues(new Uint8Array(32)),
204
205
  allowCredentials: [],
@@ -282,32 +283,29 @@ async function getPrfSecret(credentialId, options) {
282
283
  * password-protected private key. The private key is wrapped with a password-
283
284
  * derived AES-GCM key and stored alongside the public key (SPKI).
284
285
  *
285
- * This is a minimal, browser-oriented implementation designed to provide a
286
- * practical alternative while keeping crypto surface area focused and safe.
286
+ * This implementation uses @noble libraries for cryptographic operations,
287
+ * ensuring functionality even when Web Crypto API is unavailable.
287
288
  */
288
289
  function toBase64(bytes) {
289
- const arr = new Uint8Array(bytes);
290
290
  let binary = "";
291
- for (let i = 0; i < arr.byteLength; i++) binary += String.fromCharCode(arr[i]);
291
+ for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
292
292
  if (typeof window !== "undefined" && window.btoa) return window.btoa(binary);
293
- return Buffer.from(binary, "binary").toString("base64");
293
+ return btoa(binary);
294
294
  }
295
295
  function fromBase64(b64) {
296
296
  if (typeof window !== "undefined" && window.atob) {
297
- const bin = window.atob(b64);
298
- const bytes = new Uint8Array(bin.length);
299
- for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
300
- return bytes;
301
- }
302
- return new Uint8Array(Buffer.from(b64, "base64"));
297
+ const bin$1 = window.atob(b64);
298
+ const bytes$1 = new Uint8Array(bin$1.length);
299
+ for (let i = 0; i < bin$1.length; i++) bytes$1[i] = bin$1.charCodeAt(i);
300
+ return bytes$1;
301
+ }
302
+ const bin = atob(b64);
303
+ const bytes = new Uint8Array(bin.length);
304
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
305
+ return bytes;
303
306
  }
304
307
  async function checkPRFSupport() {
305
308
  try {
306
- const cryptoObj = typeof window !== "undefined" && window.crypto || globalThis.crypto;
307
- if (!cryptoObj || !cryptoObj.subtle) {
308
- console.log("Web Crypto API not available, falling back to password-protected key");
309
- return false;
310
- }
311
309
  const supported = (typeof PublicKeyCredential !== "undefined" ? await PublicKeyCredential.getClientCapabilities() : null)?.["extension:prf"] === true;
312
310
  if (supported) console.log("PRF extension is supported.");
313
311
  else console.log("PRF extension is not supported.");
@@ -318,107 +316,43 @@ async function checkPRFSupport() {
318
316
  }
319
317
  }
320
318
  async function generatePasswordProtectedKey(password) {
321
- const cryptoObj = typeof window !== "undefined" && window.crypto || globalThis.crypto;
322
- if (!cryptoObj || !cryptoObj.subtle) throw new Error("Web Crypto API not available");
323
- const kp = await cryptoObj.subtle.generateKey({
324
- name: "ECDSA",
325
- namedCurve: "P-256"
326
- }, true, ["sign"]);
327
- const publicKey = kp.publicKey;
328
- const privateKey = kp.privateKey;
329
- const publicKeySpkiBase64 = toBase64(await cryptoObj.subtle.exportKey("spki", publicKey));
330
- const privateKeyJwk = await cryptoObj.subtle.exportKey("jwk", privateKey);
331
- const jwkStr = JSON.stringify(privateKeyJwk);
332
- const salt = cryptoObj.getRandomValues(new Uint8Array(16));
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, ["encrypt"]);
343
- const iv = cryptoObj.getRandomValues(new Uint8Array(12));
344
- const enc = new TextEncoder().encode(jwkStr);
319
+ const privateKey = _noble_secp256k1.utils.randomPrivateKey();
320
+ const publicKeySpkiBase64 = toBase64(_noble_secp256k1.getPublicKey(privateKey, true));
321
+ const salt = (0, _noble_secp256k1_utils.randomBytes)(16);
322
+ const derivedKey = (0, _noble_hashes.pbkdf2)(_noble_hashes.sha256, new TextEncoder().encode(password), salt, {
323
+ c: 1e5,
324
+ dkLen: 32
325
+ });
326
+ const iv = (0, _noble_secp256k1_utils.randomBytes)(12);
345
327
  return {
346
328
  publicKeySpkiBase64,
347
- wrappedPrivateKeyBase64: toBase64(await cryptoObj.subtle.encrypt({
348
- name: "AES-GCM",
349
- iv
350
- }, aesKey, enc)),
351
- saltBase64: toBase64(salt.buffer),
352
- ivBase64: toBase64(iv.buffer)
329
+ wrappedPrivateKeyBase64: toBase64((0, _noble_ciphers.aesGcm)(derivedKey, iv).encrypt(privateKey)),
330
+ saltBase64: toBase64(salt),
331
+ ivBase64: toBase64(iv)
353
332
  };
354
333
  }
355
334
  async function unwrapPasswordProtectedPrivateKey(bundle, password) {
356
- const cryptoObj = typeof window !== "undefined" && window.crypto || globalThis.crypto;
357
- if (!cryptoObj || !cryptoObj.subtle) throw new Error("Web Crypto API not available");
358
335
  const salt = fromBase64(bundle.saltBase64);
359
- const pbKey = await cryptoObj.subtle.importKey("raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveKey"]);
360
- const aesKey = await cryptoObj.subtle.deriveKey({
361
- name: "PBKDF2",
362
- salt: salt.slice().buffer,
363
- iterations: 1e5,
364
- hash: "SHA-256"
365
- }, pbKey, {
366
- name: "AES-GCM",
367
- length: 256
368
- }, false, ["decrypt"]);
336
+ const derivedKey = (0, _noble_hashes.pbkdf2)(_noble_hashes.sha256, new TextEncoder().encode(password), salt, {
337
+ c: 1e5,
338
+ dkLen: 32
339
+ });
369
340
  const iv = fromBase64(bundle.ivBase64);
370
341
  const ct = fromBase64(bundle.wrappedPrivateKeyBase64);
371
- const jwkBytes = await cryptoObj.subtle.decrypt({
372
- name: "AES-GCM",
373
- iv: iv.slice()
374
- }, aesKey, ct.slice());
375
- const jwkStr = new TextDecoder().decode(jwkBytes);
376
- const privateKeyJwk = JSON.parse(jwkStr);
377
- await cryptoObj.subtle.importKey("jwk", privateKeyJwk, {
378
- name: "ECDSA",
379
- namedCurve: "P-256"
380
- }, true, ["sign"]);
381
- const d = privateKeyJwk.d;
382
- if (!d) throw new Error("Missing private scalar in JWK");
383
- return base64UrlToBytes(d);
384
- }
385
- function base64UrlToBytes(base64url) {
386
- let base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
387
- const pad = base64.length % 4;
388
- if (pad) base64 += "=".repeat(4 - pad);
389
- if (typeof window !== "undefined" && window.atob) {
390
- const binary = window.atob(base64);
391
- const bytes = new Uint8Array(binary.length);
392
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
393
- return bytes;
394
- }
395
- return new Uint8Array(Buffer.from(base64, "base64"));
342
+ return (0, _noble_ciphers.aesGcm)(derivedKey, iv).decrypt(ct);
396
343
  }
397
- async function importPublicKeyFromBundle(bundle) {
398
- const cryptoObj = typeof window !== "undefined" && window.crypto || globalThis.crypto;
399
- if (!cryptoObj || !cryptoObj.subtle) throw new Error("Web Crypto API not available");
400
- const spki = fromBase64(bundle.publicKeySpkiBase64);
401
- return await cryptoObj.subtle.importKey("spki", spki, {
402
- name: "ECDSA",
403
- namedCurve: "P-256"
404
- }, true, ["verify"]);
344
+ function importPublicKeyFromBundle(bundle) {
345
+ return fromBase64(bundle.publicKeySpkiBase64);
405
346
  }
406
347
  const DEFAULT_SALT = "nostr-key-derivation";
407
- async function deriveNostrPrivateKey(password, salt = DEFAULT_SALT) {
408
- const cryptoObj = typeof window !== "undefined" && window.crypto || globalThis.crypto;
409
- if (!cryptoObj || !cryptoObj.subtle) throw new Error("Web Crypto API not available");
410
- const encoder = new TextEncoder();
411
- const passwordKey = await cryptoObj.subtle.importKey("raw", encoder.encode(password), "PBKDF2", false, ["deriveBits"]);
412
- const derivedBits = await cryptoObj.subtle.deriveBits({
413
- name: "PBKDF2",
414
- salt: encoder.encode(salt),
415
- iterations: 1e5,
416
- hash: "SHA-256"
417
- }, passwordKey, 256);
418
- return new Uint8Array(derivedBits);
348
+ function deriveNostrPrivateKey(password, salt = DEFAULT_SALT) {
349
+ return (0, _noble_hashes.pbkdf2)(_noble_hashes.sha256, new TextEncoder().encode(password), new TextEncoder().encode(salt), {
350
+ c: 1e5,
351
+ dkLen: 32
352
+ });
419
353
  }
420
- async function getPublicKeyFromPassword(password, salt = DEFAULT_SALT) {
421
- return (0, applesauce_core_helpers.getPublicKey)(await deriveNostrPrivateKey(password, salt));
354
+ function getPublicKeyFromPassword(password, salt = DEFAULT_SALT) {
355
+ return (0, applesauce_core_helpers.getPublicKey)(deriveNostrPrivateKey(password, salt));
422
356
  }
423
357
 
424
358
  //#endregion
@@ -701,7 +635,7 @@ var NosskeyManager = class {
701
635
  */
702
636
  async createPasswordProtectedNostrKey(password, options = {}) {
703
637
  const salt = await deriveSaltFromUsername(options.username);
704
- const pubkey = await getPublicKeyFromPassword(password, salt);
638
+ const pubkey = getPublicKeyFromPassword(password, salt);
705
639
  return {
706
640
  credentialId: bytesToHex((typeof window !== "undefined" ? window.crypto : globalThis.crypto).getRandomValues(new Uint8Array(16))),
707
641
  pubkey,
@@ -722,7 +656,7 @@ var NosskeyManager = class {
722
656
  if (isPasswordDerived) {
723
657
  if (shouldUseCache) sk = this.#keyCache.getKey(keyInfo.credentialId);
724
658
  if (!sk && password) {
725
- sk = await deriveNostrPrivateKey(password, keyInfo.salt);
659
+ sk = deriveNostrPrivateKey(password, keyInfo.salt);
726
660
  if (shouldUseCache) this.#keyCache.setKey(keyInfo.credentialId, sk);
727
661
  }
728
662
  if (!sk) throw new Error("Password required - key not in cache. Provide password to sign.");
@@ -763,7 +697,7 @@ var NosskeyManager = class {
763
697
  }
764
698
  if (!keyInfo.passwordProtectedBundle && keyInfo.salt) {
765
699
  if (!options.password) throw new Error("Password is required for password-derived keys");
766
- return bytesToHex(await deriveNostrPrivateKey(options.password, keyInfo.salt));
700
+ return bytesToHex(deriveNostrPrivateKey(options.password, keyInfo.salt));
767
701
  }
768
702
  let usedCredentialId = credentialId;
769
703
  if (!usedCredentialId && keyInfo.credentialId) usedCredentialId = hexToBytes(keyInfo.credentialId);
@@ -788,8 +722,8 @@ var NosskeyManager = class {
788
722
  if (keyInfo.recovery) throw new Error("Recovery already configured");
789
723
  if (!(currentCredentialId || (keyInfo.credentialId ? hexToBytes(keyInfo.credentialId) : void 0))) throw new Error("Credential ID required");
790
724
  const recoverySalt = bytesToHex((typeof window !== "undefined" ? window.crypto : globalThis.crypto).getRandomValues(new Uint8Array(16)));
791
- const recoveryPubkey = await getPublicKeyFromPassword(password, recoverySalt);
792
- const recoverySk = await deriveNostrPrivateKey(password, recoverySalt);
725
+ const recoveryPubkey = getPublicKeyFromPassword(password, recoverySalt);
726
+ const recoverySk = deriveNostrPrivateKey(password, recoverySalt);
793
727
  const signature = this.#signWithKey(recoverySk, keyInfo.pubkey);
794
728
  this.#clearKey(recoverySk);
795
729
  const updatedKeyInfo = {
@@ -824,13 +758,13 @@ var NosskeyManager = class {
824
758
  if (!keyInfo.recovery) throw new Error("No recovery key configured");
825
759
  if (!newCredentialId) throw new Error("New credential ID is required for recovery");
826
760
  const { recoveryPubkey, recoverySalt } = keyInfo.recovery;
827
- if (await getPublicKeyFromPassword(password, recoverySalt) !== recoveryPubkey) throw new Error("Invalid recovery password");
761
+ if (getPublicKeyFromPassword(password, recoverySalt) !== recoveryPubkey) throw new Error("Invalid recovery password");
828
762
  const { secret: newSk } = await getPrfSecret(newCredentialId, this.#prfOptions);
829
763
  const newPubkey = (0, applesauce_core_helpers.getPublicKey)(newSk);
830
764
  this.#clearKey(newSk);
831
765
  const newRecoverySalt = bytesToHex((typeof window !== "undefined" ? window.crypto : globalThis.crypto).getRandomValues(new Uint8Array(16)));
832
- const newRecoveryPubkey = await getPublicKeyFromPassword(password, newRecoverySalt);
833
- const recoverySk = await deriveNostrPrivateKey(password, recoverySalt);
766
+ const newRecoveryPubkey = getPublicKeyFromPassword(password, newRecoverySalt);
767
+ const recoverySk = deriveNostrPrivateKey(password, recoverySalt);
834
768
  const signature = this.#signWithKey(recoverySk, newPubkey);
835
769
  this.#clearKey(recoverySk);
836
770
  const updatedKeyInfo = {