diff --git a/src/event.test.ts b/src/event.test.ts index e69de29..b0b0462 100644 --- a/src/event.test.ts +++ b/src/event.test.ts @@ -0,0 +1,2 @@ +import { test } from "vitest"; +test("placeholder", () => {}); diff --git a/src/filter.test.ts b/src/filter.test.ts index e69de29..b0b0462 100644 --- a/src/filter.test.ts +++ b/src/filter.test.ts @@ -0,0 +1,2 @@ +import { test } from "vitest"; +test("placeholder", () => {}); diff --git a/src/id.test.ts b/src/id.test.ts index e69de29..b0b0462 100644 --- a/src/id.test.ts +++ b/src/id.test.ts @@ -0,0 +1,2 @@ +import { test } from "vitest"; +test("placeholder", () => {}); diff --git a/src/keys.test.ts b/src/keys.test.ts index e69de29..8d4e39d 100644 --- a/src/keys.test.ts +++ b/src/keys.test.ts @@ -0,0 +1,46 @@ +import { describe, test, expect } from "vitest"; +import { Keys } from "./keys"; +import { HEX_64_PATTERN } from "./constants"; +import { testSK, testPK } from "./util.test"; + +describe("Keys.generatePrivate", () => { + test("returns 64 hex characters", () => { + const privateKey = Keys.generatePrivate(); + expect(privateKey).toMatch(HEX_64_PATTERN); + }); + + test("generates unique keys", () => { + const key1 = Keys.generatePrivate(); + const key2 = Keys.generatePrivate(); + expect(key1).not.toBe(key2); + }); +}); + +describe("Keys.getPublic", () => { + test("derives correct public key", () => { + const publicKey = Keys.getPublic(testSK); + expect(publicKey).toBe(testPK); + }); + + test("throws on invalid private key - too short", () => { + expect(() => Keys.getPublic("abc123")).toThrow( + "private key must be 64 lowercase hex characters", + ); + }); + + test("throws on invalid private key - non-hex", () => { + expect(() => + Keys.getPublic( + "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", + ), + ).toThrow("private key must be 64 lowercase hex characters"); + }); + + test("throws on invalid private key - uppercase", () => { + expect(() => + Keys.getPublic( + "F43A0435F69529F310BBD1D6263D2FBF0977F54BFE2310CC37AE5904B83BB167", + ), + ).toThrow("private key must be 64 lowercase hex characters"); + }); +}); diff --git a/src/keys.ts b/src/keys.ts index e69de29..be54515 100644 --- a/src/keys.ts +++ b/src/keys.ts @@ -0,0 +1,34 @@ +import { schnorr } from "@noble/secp256k1"; +import { HEX_64_PATTERN } from "./constants"; +import { MalformedPrivKeyError } from "./errors"; + +/** + * Generates a new random secp256k1 private key. + * @returns 64-character lowercase hexadecimal string + */ +function generatePrivate(): string { + const { secretKey } = schnorr.keygen(); + return Buffer.from(secretKey).toString("hex"); +} + +/** + * Derives the public key from a private key hex string. + * @param privateKey - 64-character lowercase hexadecimal private key + * @returns 64-character lowercase hexadecimal public key (x-coordinate only) + * @throws {MalformedPrivKeyError} If private key is not 64 lowercase hex characters + */ +function getPublic(privateKey: string): string { + if (!HEX_64_PATTERN.test(privateKey)) { + throw new MalformedPrivKeyError(); + } + + const privateKeyBytes = Buffer.from(privateKey, "hex"); + const publicKeyBytes = schnorr.getPublicKey(privateKeyBytes); + + return Buffer.from(publicKeyBytes).toString("hex"); +} + +export const Keys = { + generatePrivate, + getPublic, +}; diff --git a/src/sign.test.ts b/src/sign.test.ts index e69de29..b0b0462 100644 --- a/src/sign.test.ts +++ b/src/sign.test.ts @@ -0,0 +1,2 @@ +import { test } from "vitest"; +test("placeholder", () => {}); diff --git a/src/util.test.ts b/src/util.test.ts new file mode 100644 index 0000000..2cc7a3a --- /dev/null +++ b/src/util.test.ts @@ -0,0 +1,8 @@ +import { test } from "vitest"; + +export const testSK = + "f43a0435f69529f310bbd1d6263d2fbf0977f54bfe2310cc37ae5904b83bb167"; +export const testPK = + "cfa87f35acbde29ba1ab3ee42de527b2cad33ac487e80cf2d6405ea0042c8fef"; + +test("placeholder", () => {}); diff --git a/src/validate.test.ts b/src/validate.test.ts index e69de29..b0b0462 100644 --- a/src/validate.test.ts +++ b/src/validate.test.ts @@ -0,0 +1,2 @@ +import { test } from "vitest"; +test("placeholder", () => {});