ns-auth-sdk 1.12.3 → 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.cjs +50 -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 +52 -117
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
package/dist/index.cjs
CHANGED
|
@@ -28,6 +28,10 @@ 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_sha2 = require("@noble/hashes/sha2");
|
|
32
|
+
let _noble_hashes_pbkdf2 = require("@noble/hashes/pbkdf2");
|
|
33
|
+
let _noble_ciphers_aes = require("@noble/ciphers/aes");
|
|
34
|
+
let _noble_secp256k1_utils = require("@noble/secp256k1/utils");
|
|
31
35
|
|
|
32
36
|
//#region src/utils/utils.ts
|
|
33
37
|
/**
|
|
@@ -197,8 +201,6 @@ const PRF_EVAL_INPUT = new TextEncoder().encode("nostr-pwk");
|
|
|
197
201
|
*/
|
|
198
202
|
async function isPrfSupported() {
|
|
199
203
|
try {
|
|
200
|
-
const cryptoObj = typeof window !== "undefined" && window.crypto || globalThis.crypto;
|
|
201
|
-
if (!cryptoObj || !cryptoObj.subtle) return false;
|
|
202
204
|
const response = await navigator.credentials.get({ publicKey: {
|
|
203
205
|
challenge: crypto.getRandomValues(new Uint8Array(32)),
|
|
204
206
|
allowCredentials: [],
|
|
@@ -282,32 +284,29 @@ async function getPrfSecret(credentialId, options) {
|
|
|
282
284
|
* password-protected private key. The private key is wrapped with a password-
|
|
283
285
|
* derived AES-GCM key and stored alongside the public key (SPKI).
|
|
284
286
|
*
|
|
285
|
-
* This
|
|
286
|
-
*
|
|
287
|
+
* This implementation uses @noble libraries for cryptographic operations,
|
|
288
|
+
* ensuring functionality even when Web Crypto API is unavailable.
|
|
287
289
|
*/
|
|
288
290
|
function toBase64(bytes) {
|
|
289
|
-
const arr = new Uint8Array(bytes);
|
|
290
291
|
let binary = "";
|
|
291
|
-
for (let i = 0; i <
|
|
292
|
+
for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
|
|
292
293
|
if (typeof window !== "undefined" && window.btoa) return window.btoa(binary);
|
|
293
|
-
return
|
|
294
|
+
return btoa(binary);
|
|
294
295
|
}
|
|
295
296
|
function fromBase64(b64) {
|
|
296
297
|
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
|
-
|
|
298
|
+
const bin$1 = window.atob(b64);
|
|
299
|
+
const bytes$1 = new Uint8Array(bin$1.length);
|
|
300
|
+
for (let i = 0; i < bin$1.length; i++) bytes$1[i] = bin$1.charCodeAt(i);
|
|
301
|
+
return bytes$1;
|
|
302
|
+
}
|
|
303
|
+
const bin = atob(b64);
|
|
304
|
+
const bytes = new Uint8Array(bin.length);
|
|
305
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
306
|
+
return bytes;
|
|
303
307
|
}
|
|
304
308
|
async function checkPRFSupport() {
|
|
305
309
|
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
310
|
const supported = (typeof PublicKeyCredential !== "undefined" ? await PublicKeyCredential.getClientCapabilities() : null)?.["extension:prf"] === true;
|
|
312
311
|
if (supported) console.log("PRF extension is supported.");
|
|
313
312
|
else console.log("PRF extension is not supported.");
|
|
@@ -318,107 +317,43 @@ async function checkPRFSupport() {
|
|
|
318
317
|
}
|
|
319
318
|
}
|
|
320
319
|
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);
|
|
320
|
+
const privateKey = _noble_secp256k1.utils.randomPrivateKey();
|
|
321
|
+
const publicKeySpkiBase64 = toBase64(_noble_secp256k1.getPublicKey(privateKey, true));
|
|
322
|
+
const salt = (0, _noble_secp256k1_utils.randomBytes)(16);
|
|
323
|
+
const derivedKey = (0, _noble_hashes_pbkdf2.pbkdf2)(_noble_hashes_sha2.sha256, new TextEncoder().encode(password), salt, {
|
|
324
|
+
c: 1e5,
|
|
325
|
+
dkLen: 32
|
|
326
|
+
});
|
|
327
|
+
const iv = (0, _noble_secp256k1_utils.randomBytes)(12);
|
|
345
328
|
return {
|
|
346
329
|
publicKeySpkiBase64,
|
|
347
|
-
wrappedPrivateKeyBase64: toBase64(
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}, aesKey, enc)),
|
|
351
|
-
saltBase64: toBase64(salt.buffer),
|
|
352
|
-
ivBase64: toBase64(iv.buffer)
|
|
330
|
+
wrappedPrivateKeyBase64: toBase64((0, _noble_ciphers_aes.aesGcm)(derivedKey, iv).encrypt(privateKey)),
|
|
331
|
+
saltBase64: toBase64(salt),
|
|
332
|
+
ivBase64: toBase64(iv)
|
|
353
333
|
};
|
|
354
334
|
}
|
|
355
335
|
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
336
|
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"]);
|
|
337
|
+
const derivedKey = (0, _noble_hashes_pbkdf2.pbkdf2)(_noble_hashes_sha2.sha256, new TextEncoder().encode(password), salt, {
|
|
338
|
+
c: 1e5,
|
|
339
|
+
dkLen: 32
|
|
340
|
+
});
|
|
369
341
|
const iv = fromBase64(bundle.ivBase64);
|
|
370
342
|
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"));
|
|
343
|
+
return (0, _noble_ciphers_aes.aesGcm)(derivedKey, iv).decrypt(ct);
|
|
396
344
|
}
|
|
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"]);
|
|
345
|
+
function importPublicKeyFromBundle(bundle) {
|
|
346
|
+
return fromBase64(bundle.publicKeySpkiBase64);
|
|
405
347
|
}
|
|
406
348
|
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);
|
|
349
|
+
function deriveNostrPrivateKey(password, salt = DEFAULT_SALT) {
|
|
350
|
+
return (0, _noble_hashes_pbkdf2.pbkdf2)(_noble_hashes_sha2.sha256, new TextEncoder().encode(password), new TextEncoder().encode(salt), {
|
|
351
|
+
c: 1e5,
|
|
352
|
+
dkLen: 32
|
|
353
|
+
});
|
|
419
354
|
}
|
|
420
|
-
|
|
421
|
-
return (0, applesauce_core_helpers.getPublicKey)(
|
|
355
|
+
function getPublicKeyFromPassword(password, salt = DEFAULT_SALT) {
|
|
356
|
+
return (0, applesauce_core_helpers.getPublicKey)(deriveNostrPrivateKey(password, salt));
|
|
422
357
|
}
|
|
423
358
|
|
|
424
359
|
//#endregion
|
|
@@ -701,7 +636,7 @@ var NosskeyManager = class {
|
|
|
701
636
|
*/
|
|
702
637
|
async createPasswordProtectedNostrKey(password, options = {}) {
|
|
703
638
|
const salt = await deriveSaltFromUsername(options.username);
|
|
704
|
-
const pubkey =
|
|
639
|
+
const pubkey = getPublicKeyFromPassword(password, salt);
|
|
705
640
|
return {
|
|
706
641
|
credentialId: bytesToHex((typeof window !== "undefined" ? window.crypto : globalThis.crypto).getRandomValues(new Uint8Array(16))),
|
|
707
642
|
pubkey,
|
|
@@ -722,7 +657,7 @@ var NosskeyManager = class {
|
|
|
722
657
|
if (isPasswordDerived) {
|
|
723
658
|
if (shouldUseCache) sk = this.#keyCache.getKey(keyInfo.credentialId);
|
|
724
659
|
if (!sk && password) {
|
|
725
|
-
sk =
|
|
660
|
+
sk = deriveNostrPrivateKey(password, keyInfo.salt);
|
|
726
661
|
if (shouldUseCache) this.#keyCache.setKey(keyInfo.credentialId, sk);
|
|
727
662
|
}
|
|
728
663
|
if (!sk) throw new Error("Password required - key not in cache. Provide password to sign.");
|
|
@@ -763,7 +698,7 @@ var NosskeyManager = class {
|
|
|
763
698
|
}
|
|
764
699
|
if (!keyInfo.passwordProtectedBundle && keyInfo.salt) {
|
|
765
700
|
if (!options.password) throw new Error("Password is required for password-derived keys");
|
|
766
|
-
return bytesToHex(
|
|
701
|
+
return bytesToHex(deriveNostrPrivateKey(options.password, keyInfo.salt));
|
|
767
702
|
}
|
|
768
703
|
let usedCredentialId = credentialId;
|
|
769
704
|
if (!usedCredentialId && keyInfo.credentialId) usedCredentialId = hexToBytes(keyInfo.credentialId);
|
|
@@ -788,8 +723,8 @@ var NosskeyManager = class {
|
|
|
788
723
|
if (keyInfo.recovery) throw new Error("Recovery already configured");
|
|
789
724
|
if (!(currentCredentialId || (keyInfo.credentialId ? hexToBytes(keyInfo.credentialId) : void 0))) throw new Error("Credential ID required");
|
|
790
725
|
const recoverySalt = bytesToHex((typeof window !== "undefined" ? window.crypto : globalThis.crypto).getRandomValues(new Uint8Array(16)));
|
|
791
|
-
const recoveryPubkey =
|
|
792
|
-
const recoverySk =
|
|
726
|
+
const recoveryPubkey = getPublicKeyFromPassword(password, recoverySalt);
|
|
727
|
+
const recoverySk = deriveNostrPrivateKey(password, recoverySalt);
|
|
793
728
|
const signature = this.#signWithKey(recoverySk, keyInfo.pubkey);
|
|
794
729
|
this.#clearKey(recoverySk);
|
|
795
730
|
const updatedKeyInfo = {
|
|
@@ -824,13 +759,13 @@ var NosskeyManager = class {
|
|
|
824
759
|
if (!keyInfo.recovery) throw new Error("No recovery key configured");
|
|
825
760
|
if (!newCredentialId) throw new Error("New credential ID is required for recovery");
|
|
826
761
|
const { recoveryPubkey, recoverySalt } = keyInfo.recovery;
|
|
827
|
-
if (
|
|
762
|
+
if (getPublicKeyFromPassword(password, recoverySalt) !== recoveryPubkey) throw new Error("Invalid recovery password");
|
|
828
763
|
const { secret: newSk } = await getPrfSecret(newCredentialId, this.#prfOptions);
|
|
829
764
|
const newPubkey = (0, applesauce_core_helpers.getPublicKey)(newSk);
|
|
830
765
|
this.#clearKey(newSk);
|
|
831
766
|
const newRecoverySalt = bytesToHex((typeof window !== "undefined" ? window.crypto : globalThis.crypto).getRandomValues(new Uint8Array(16)));
|
|
832
|
-
const newRecoveryPubkey =
|
|
833
|
-
const recoverySk =
|
|
767
|
+
const newRecoveryPubkey = getPublicKeyFromPassword(password, newRecoverySalt);
|
|
768
|
+
const recoverySk = deriveNostrPrivateKey(password, recoverySalt);
|
|
834
769
|
const signature = this.#signWithKey(recoverySk, newPubkey);
|
|
835
770
|
this.#clearKey(recoverySk);
|
|
836
771
|
const updatedKeyInfo = {
|