200 lines
6.6 KiB
TypeScript
200 lines
6.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
validatePrivateKey,
|
|
getPublicKeyFromPrivate,
|
|
isValidPubkey,
|
|
normalizePubkey,
|
|
pubkeyToNpub,
|
|
} from "./nostr-bus.js";
|
|
|
|
// Test private key (DO NOT use in production - this is a known test key)
|
|
const TEST_HEX_KEY = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
|
const TEST_NSEC = "nsec1qypqxpq9qtpqscx7peytzfwtdjmcv0mrz5rjpej8vjppfkqfqy8skqfv3l";
|
|
|
|
describe("validatePrivateKey", () => {
|
|
describe("hex format", () => {
|
|
it("accepts valid 64-char hex key", () => {
|
|
const result = validatePrivateKey(TEST_HEX_KEY);
|
|
expect(result).toBeInstanceOf(Uint8Array);
|
|
expect(result.length).toBe(32);
|
|
});
|
|
|
|
it("accepts lowercase hex", () => {
|
|
const result = validatePrivateKey(TEST_HEX_KEY.toLowerCase());
|
|
expect(result).toBeInstanceOf(Uint8Array);
|
|
});
|
|
|
|
it("accepts uppercase hex", () => {
|
|
const result = validatePrivateKey(TEST_HEX_KEY.toUpperCase());
|
|
expect(result).toBeInstanceOf(Uint8Array);
|
|
});
|
|
|
|
it("accepts mixed case hex", () => {
|
|
const mixed = "0123456789ABCdef0123456789abcDEF0123456789abcdef0123456789ABCDEF";
|
|
const result = validatePrivateKey(mixed);
|
|
expect(result).toBeInstanceOf(Uint8Array);
|
|
});
|
|
|
|
it("trims whitespace", () => {
|
|
const result = validatePrivateKey(` ${TEST_HEX_KEY} `);
|
|
expect(result).toBeInstanceOf(Uint8Array);
|
|
});
|
|
|
|
it("trims newlines", () => {
|
|
const result = validatePrivateKey(`${TEST_HEX_KEY}\n`);
|
|
expect(result).toBeInstanceOf(Uint8Array);
|
|
});
|
|
|
|
it("rejects 63-char hex (too short)", () => {
|
|
expect(() => validatePrivateKey(TEST_HEX_KEY.slice(0, 63))).toThrow(
|
|
"Private key must be 64 hex characters"
|
|
);
|
|
});
|
|
|
|
it("rejects 65-char hex (too long)", () => {
|
|
expect(() => validatePrivateKey(TEST_HEX_KEY + "0")).toThrow(
|
|
"Private key must be 64 hex characters"
|
|
);
|
|
});
|
|
|
|
it("rejects non-hex characters", () => {
|
|
const invalid = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdeg"; // 'g' at end
|
|
expect(() => validatePrivateKey(invalid)).toThrow("Private key must be 64 hex characters");
|
|
});
|
|
|
|
it("rejects empty string", () => {
|
|
expect(() => validatePrivateKey("")).toThrow("Private key must be 64 hex characters");
|
|
});
|
|
|
|
it("rejects whitespace-only string", () => {
|
|
expect(() => validatePrivateKey(" ")).toThrow("Private key must be 64 hex characters");
|
|
});
|
|
|
|
it("rejects key with 0x prefix", () => {
|
|
expect(() => validatePrivateKey("0x" + TEST_HEX_KEY)).toThrow(
|
|
"Private key must be 64 hex characters"
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("nsec format", () => {
|
|
it("rejects invalid nsec (wrong checksum)", () => {
|
|
const badNsec = "nsec1invalidinvalidinvalidinvalidinvalidinvalidinvalidinvalid";
|
|
expect(() => validatePrivateKey(badNsec)).toThrow();
|
|
});
|
|
|
|
it("rejects npub (wrong type)", () => {
|
|
const npub = "npub1qypqxpq9qtpqscx7peytzfwtdjmcv0mrz5rjpej8vjppfkqfqy8s5epk55";
|
|
expect(() => validatePrivateKey(npub)).toThrow();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("isValidPubkey", () => {
|
|
describe("hex format", () => {
|
|
it("accepts valid 64-char hex pubkey", () => {
|
|
const validHex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
|
expect(isValidPubkey(validHex)).toBe(true);
|
|
});
|
|
|
|
it("accepts uppercase hex", () => {
|
|
const validHex = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
|
|
expect(isValidPubkey(validHex)).toBe(true);
|
|
});
|
|
|
|
it("rejects 63-char hex", () => {
|
|
const shortHex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde";
|
|
expect(isValidPubkey(shortHex)).toBe(false);
|
|
});
|
|
|
|
it("rejects 65-char hex", () => {
|
|
const longHex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0";
|
|
expect(isValidPubkey(longHex)).toBe(false);
|
|
});
|
|
|
|
it("rejects non-hex characters", () => {
|
|
const invalid = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdeg";
|
|
expect(isValidPubkey(invalid)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("npub format", () => {
|
|
it("rejects invalid npub", () => {
|
|
expect(isValidPubkey("npub1invalid")).toBe(false);
|
|
});
|
|
|
|
it("rejects nsec (wrong type)", () => {
|
|
expect(isValidPubkey(TEST_NSEC)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("edge cases", () => {
|
|
it("rejects empty string", () => {
|
|
expect(isValidPubkey("")).toBe(false);
|
|
});
|
|
|
|
it("handles whitespace-padded input", () => {
|
|
const validHex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
|
expect(isValidPubkey(` ${validHex} `)).toBe(true);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("normalizePubkey", () => {
|
|
describe("hex format", () => {
|
|
it("lowercases hex pubkey", () => {
|
|
const upper = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
|
|
const result = normalizePubkey(upper);
|
|
expect(result).toBe(upper.toLowerCase());
|
|
});
|
|
|
|
it("trims whitespace", () => {
|
|
const hex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
|
expect(normalizePubkey(` ${hex} `)).toBe(hex);
|
|
});
|
|
|
|
it("rejects invalid hex", () => {
|
|
expect(() => normalizePubkey("invalid")).toThrow("Pubkey must be 64 hex characters");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("getPublicKeyFromPrivate", () => {
|
|
it("derives public key from hex private key", () => {
|
|
const pubkey = getPublicKeyFromPrivate(TEST_HEX_KEY);
|
|
expect(pubkey).toMatch(/^[0-9a-f]{64}$/);
|
|
expect(pubkey.length).toBe(64);
|
|
});
|
|
|
|
it("derives consistent public key", () => {
|
|
const pubkey1 = getPublicKeyFromPrivate(TEST_HEX_KEY);
|
|
const pubkey2 = getPublicKeyFromPrivate(TEST_HEX_KEY);
|
|
expect(pubkey1).toBe(pubkey2);
|
|
});
|
|
|
|
it("throws for invalid private key", () => {
|
|
expect(() => getPublicKeyFromPrivate("invalid")).toThrow();
|
|
});
|
|
});
|
|
|
|
describe("pubkeyToNpub", () => {
|
|
it("converts hex pubkey to npub format", () => {
|
|
const hex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
|
const npub = pubkeyToNpub(hex);
|
|
expect(npub).toMatch(/^npub1[a-z0-9]+$/);
|
|
});
|
|
|
|
it("produces consistent output", () => {
|
|
const hex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
|
const npub1 = pubkeyToNpub(hex);
|
|
const npub2 = pubkeyToNpub(hex);
|
|
expect(npub1).toBe(npub2);
|
|
});
|
|
|
|
it("normalizes uppercase hex first", () => {
|
|
const lower = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
|
const upper = lower.toUpperCase();
|
|
expect(pubkeyToNpub(lower)).toBe(pubkeyToNpub(upper));
|
|
});
|
|
});
|