nostr-nsec-seedphrase
Version:
A comprehensive TypeScript library for Nostr key management with BIP39 seed phrases, supporting both ESM and CommonJS. Implements NIP-01, NIP-06, NIP-19, and NIP-26 with key generation, event signing, bech32 encoding/decoding, and secure cryptographic ope
226 lines (225 loc) • 11.1 kB
JavaScript
// WARNING: These keys are for testing purposes only!
// Never use these keys in production or for real Nostr events.
// They are intentionally hardcoded to ensure deterministic testing.
// Mock dependencies
vi.mock("@noble/secp256k1", () => {
const TEST_PRIVATE_KEY = "27e2a04464f4e73b9131548b6dffbe47ae49ec7a7562c5a157e6a30f9f1ceb69";
const TEST_PUBLIC_KEY = "02030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021";
let lastSignedMessage = "";
return {
getPublicKey: () => new Uint8Array(hexToBytes(TEST_PUBLIC_KEY)),
utils: {
bytesToHex: (bytes) => Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join(''),
hexToBytes: (hex) => new Uint8Array(hex.match(/.{1,2}/g)?.map(byte => parseInt(byte, 16)) || []),
isValidPrivateKey: () => true,
},
sign: (msg) => {
lastSignedMessage = Array.from(msg).map(b => String.fromCharCode(b)).join('');
return { toCompactRawBytes: () => new Uint8Array([1, 2, 3, 4]) };
},
verify: (sig, msg, pub) => {
const msgStr = Array.from(msg).map(b => String.fromCharCode(b)).join('');
return msgStr === lastSignedMessage;
},
Signature: {
fromCompact: () => ({ toCompactRawBytes: () => new Uint8Array([1, 2, 3, 4]) })
}
};
});
vi.mock("bech32", () => ({
bech32: {
encode: (prefix, words) => prefix === "npub" ? "npub1test" : "nsec1test",
decode: (str) => {
const TEST_PRIVATE_KEY = "27e2a04464f4e73b9131548b6dffbe47ae49ec7a7562c5a157e6a30f9f1ceb69";
const TEST_PUBLIC_KEY = "02030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021";
if (str.startsWith("npub")) {
return { prefix: "npub", words: Array.from(hexToBytes(TEST_PUBLIC_KEY)) };
}
else {
return { prefix: "nsec", words: Array.from(hexToBytes(TEST_PRIVATE_KEY)) };
}
},
toWords: (data) => Array.from(data),
fromWords: (words) => new Uint8Array(words)
}
}));
// Create a test Uint8Array for our mock
const testEntropy = new Uint8Array([
39, 226, 160, 68, 100, 244, 231, 59, 145, 49, 84, 139, 109, 255, 190, 71,
174, 73, 236, 122, 117, 98, 197, 161, 87, 230, 163, 15, 159, 28, 235, 105
]);
vi.mock("bip39", () => {
return {
validateMnemonic: (phrase) => phrase === "test test test test test test test test test test test junk",
generateMnemonic: () => "test test test test test test test test test test test junk",
mnemonicToEntropy: () => "27e2a04464f4e73b9131548b6dffbe47ae49ec7a7562c5a157e6a30f9f1ceb69"
};
});
import { describe, it, expect, beforeAll, beforeEach, vi } from 'vitest';
import { generateKeyPairWithSeed, seedPhraseToKeyPair, validateSeedPhrase, nsecToHex, npubToHex, hexToNsec, hexToNpub, signMessage, verifySignature, createEvent, verifyEvent, configureHMAC, fromHex, } from "../index.js";
/**
* Tests for the nostr-nsec-seedphrase library.
*
* This suite covers:
* - BIP39 seed phrase validation
* - Key format conversions (hex ↔ nsec/npub)
* - Message signing and verification
* - Event creation and verification
* - HMAC configuration
* - Random key generation
*
* @version 0.5.0
*/
describe("nostr-nsec-seedphrase", () => {
beforeAll(() => {
// Configure HMAC before running tests
configureHMAC();
});
beforeEach(() => {
// Clear all mocks before each test
vi.clearAllMocks();
});
describe("Mocking Verification", () => {
it("should properly mock bip39", () => {
const result = validateSeedPhrase("test test test test test test test test test test test junk");
console.log("Direct mock call result:", result);
expect(result).toBe(true);
});
});
describe("Key Generation", () => {
it("should validate seed phrases", () => {
const validResult = validateSeedPhrase("test test test test test test test test test test test junk");
console.log("Valid seed phrase test result:", validResult);
expect(validResult).toBe(true);
const invalidResult = validateSeedPhrase("invalid seed phrase");
console.log("Invalid seed phrase test result:", invalidResult);
expect(invalidResult).toBe(false);
});
it("should generate a valid key pair with seed phrase", () => {
const keyPair = seedPhraseToKeyPair("test test test test test test test test test test test junk");
expect(keyPair.privateKey).toBeDefined();
expect(keyPair.publicKey).toBeDefined();
expect(keyPair.nsec).toMatch(/^nsec1/);
expect(keyPair.npub).toMatch(/^npub1/);
expect(keyPair.seedPhrase).toBe("test test test test test test test test test test test junk");
});
it("should convert seed phrase to key pair", () => {
const keyPair = seedPhraseToKeyPair("test test test test test test test test test test test junk");
expect(keyPair.privateKey).toBeDefined();
expect(keyPair.publicKey).toBeDefined();
expect(keyPair.nsec).toBeDefined();
expect(keyPair.npub).toBeDefined();
});
});
describe("Format Conversions", () => {
it("should convert between hex and nsec/npub formats", () => {
const keyPair = {
privateKey: "27e2a04464f4e73b9131548b6dffbe47ae49ec7a7562c5a157e6a30f9f1ceb69",
publicKey: "02030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021",
nsec: "nsec1test",
npub: "npub1test",
seedPhrase: ""
};
// Test hex to nsec/npub
const nsec = hexToNsec(keyPair.privateKey);
const npub = hexToNpub(keyPair.publicKey);
console.log("Converted nsec:", nsec);
console.log("Converted npub:", npub);
expect(nsec).toBe(keyPair.nsec);
expect(npub).toBe(keyPair.npub);
// Test nsec/npub to hex
const privateKeyHex = nsecToHex(keyPair.nsec);
const publicKeyHex = npubToHex(keyPair.npub);
console.log("Converted private key hex:", privateKeyHex);
console.log("Converted public key hex:", publicKeyHex);
expect(privateKeyHex).toBe(keyPair.privateKey);
expect(publicKeyHex).toBe(keyPair.publicKey);
});
it("should create key pair from hex", () => {
const originalKeyPair = {
privateKey: "27e2a04464f4e73b9131548b6dffbe47ae49ec7a7562c5a157e6a30f9f1ceb69",
publicKey: "02030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021",
nsec: "nsec1test",
npub: "npub1test",
seedPhrase: ""
};
const keyPair = fromHex(originalKeyPair.privateKey);
console.log("Created key pair from hex:", keyPair);
expect(keyPair.privateKey).toBe(originalKeyPair.privateKey);
expect(keyPair.publicKey).toBe(originalKeyPair.publicKey);
expect(keyPair.nsec).toBe(originalKeyPair.nsec);
expect(keyPair.npub).toBe(originalKeyPair.npub);
});
});
describe("Message Signing", () => {
it("should sign and verify messages", async () => {
const keyPair = seedPhraseToKeyPair("test test test test test test test test test test test junk");
const message = "Hello, Nostr!";
const signature = await signMessage(message, keyPair.privateKey);
console.log("Generated signature:", signature);
expect(signature).toBeDefined();
const isValid = await verifySignature(message, signature, keyPair.publicKey);
console.log("Verification result:", isValid);
expect(isValid).toBe(true);
});
it("should fail verification for invalid signatures", async () => {
const keyPair = seedPhraseToKeyPair("test test test test test test test test test test test junk");
const message = "Hello, Nostr!";
const wrongMessage = "Wrong message";
const signature = await signMessage(message, keyPair.privateKey);
const isValid = await verifySignature(wrongMessage, signature, keyPair.publicKey);
console.log("Verification result for wrong message:", isValid);
expect(isValid).toBe(false);
});
});
describe("Event Handling", () => {
it("should create and verify events", async () => {
const keyPair = seedPhraseToKeyPair("test test test test test test test test test test test junk");
const event = await createEvent("Hello, Nostr!", 1, keyPair.privateKey, [
["t", "test"],
]);
console.log("Created event:", event);
expect(event.pubkey).toBe(keyPair.publicKey);
expect(event.kind).toBe(1);
expect(event.content).toBe("Hello, Nostr!");
expect(event.tags).toEqual([["t", "test"]]);
expect(event.id).toBeDefined();
expect(event.sig).toBeDefined();
const isValid = await verifyEvent(event);
console.log("Verification result:", isValid);
expect(isValid).toBe(true);
});
it("should detect tampered events", async () => {
const keyPair = seedPhraseToKeyPair("test test test test test test test test test test test junk");
const event = await createEvent("Hello, Nostr!", 1, keyPair.privateKey);
event.content = "Tampered content"; // Tamper with the content
console.log("Event hash mismatch");
const isValid = await verifyEvent(event);
console.log("Verification result for tampered event:", isValid);
expect(isValid).toBe(false);
});
});
describe("HMAC Configuration", () => {
it("should configure HMAC without errors", () => {
expect(() => configureHMAC()).not.toThrow();
});
});
describe("Random Key Generation", () => {
it("should work with randomly generated keys", async () => {
const keyPair = generateKeyPairWithSeed();
// Test key format
expect(keyPair.privateKey).toMatch(/^[0-9a-f]{64}$/);
expect(keyPair.publicKey).toMatch(/^[0-9a-f]{64}$/);
expect(keyPair.nsec).toMatch(/^nsec1/);
expect(keyPair.npub).toMatch(/^npub1/);
// Test signing with random keys
const message = "Test message";
const signature = await signMessage(message, keyPair.privateKey);
const isValid = await verifySignature(message, signature, keyPair.publicKey);
expect(isValid).toBe(true);
});
});
});
function hexToBytes(hex) {
return new Uint8Array(hex.match(/.{1,2}/g)?.map(byte => parseInt(byte, 16)) || []);
}