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 +49 -115
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +5 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +51 -117
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
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
|
|
286
|
-
*
|
|
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 <
|
|
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
|
|
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
|
-
|
|
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
|
|
322
|
-
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const
|
|
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(
|
|
348
|
-
|
|
349
|
-
|
|
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
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
|
|
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
|
-
|
|
398
|
-
|
|
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
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
|
|
421
|
-
return (0, applesauce_core_helpers.getPublicKey)(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
792
|
-
const recoverySk =
|
|
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 (
|
|
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 =
|
|
833
|
-
const recoverySk =
|
|
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 = {
|