From 8371df4d8a0fa81fd79577e6085929449a568e84 Mon Sep 17 00:00:00 2001 From: Jay Date: Fri, 24 Oct 2025 10:34:48 -0400 Subject: [PATCH] Converted sign module. --- src/sign.test.ts | 24 ++++++++++++++++++++++-- src/sign.ts | 36 ++++++++++++++++++++++++++++++++++++ src/util.test.ts | 15 ++++++++++++++- 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/src/sign.test.ts b/src/sign.test.ts index b0b0462..4e51209 100644 --- a/src/sign.test.ts +++ b/src/sign.test.ts @@ -1,2 +1,22 @@ -import { test } from "vitest"; -test("placeholder", () => {}); +import { describe, test, expect } from "vitest"; +import { Sign } from "./sign"; +import { testSK, testEvent } from "./util.test"; + +describe("Sign.sign", () => { + test("produces correct signature", () => { + const signature = Sign.sign(testEvent.id, testSK); + expect(signature).toBe(testEvent.sig); + }); + + test("throws on invalid event ID", () => { + expect(() => Sign.sign("thisisabadeventid", testSK)).toThrow( + "event id must be 64 hex characters", + ); + }); + + test("throws on invalid private key", () => { + expect(() => Sign.sign(testEvent.id, "thisisabadsecretkey")).toThrow( + "private key must be 64 lowercase hex characters", + ); + }); +}); diff --git a/src/sign.ts b/src/sign.ts index e69de29..ece5344 100644 --- a/src/sign.ts +++ b/src/sign.ts @@ -0,0 +1,36 @@ +import * as secp from "@noble/secp256k1"; +import { schnorr } from "@noble/secp256k1"; +import { hmac } from "@noble/hashes/hmac.js"; +import { sha256 } from "@noble/hashes/sha2.js"; +import { MalformedIDError, MalformedPrivKeyError } from "./errors"; + +secp.hashes.hmacSha256 = (key, msg) => hmac(sha256, key, msg); +secp.hashes.sha256 = sha256; + +/** + * Generates a Schnorr signature for the given event ID using the provided private key. + * @param eventID - 64-character lowercase hexadecimal event ID + * @param privateKey - 64-character lowercase hexadecimal private key + * @returns 128-character lowercase hexadecimal signature + * @throws {MalformedIDError} If event ID is not 64 hex characters + * @throws {MalformedPrivKeyError} If private key is not 64 lowercase hex characters + */ +function sign(eventID: string, privateKey: string): string { + const privateKeyBytes = Buffer.from(privateKey, "hex"); + if (privateKeyBytes.length !== 32) { + throw new MalformedPrivKeyError(); + } + + const idBytes = Buffer.from(eventID, "hex"); + if (idBytes.length !== 32) { + throw new MalformedIDError(); + } + + const auxRand = sha256(privateKeyBytes); + const signature = schnorr.sign(idBytes, privateKeyBytes, auxRand); + return Buffer.from(signature).toString("hex"); +} + +export const Sign = { + sign, +}; diff --git a/src/util.test.ts b/src/util.test.ts index 2cc7a3a..2677400 100644 --- a/src/util.test.ts +++ b/src/util.test.ts @@ -1,8 +1,21 @@ import { test } from "vitest"; +import type { Event } from "./types"; + +test("placeholder", () => {}); export const testSK = "f43a0435f69529f310bbd1d6263d2fbf0977f54bfe2310cc37ae5904b83bb167"; export const testPK = "cfa87f35acbde29ba1ab3ee42de527b2cad33ac487e80cf2d6405ea0042c8fef"; -test("placeholder", () => {}); +export const testEvent: Event = { + id: "c7a702e6158744ca03508bbb4c90f9dbb0d6e88fefbfaa511d5ab24b4e3c48ad", + pubkey: testPK, + created_at: 1760740551, + kind: 1, + tags: [], + content: "hello world", + sig: "0fb7c1eaa867c4d16000587f2fb26c0b67e7e069f35d1acbb2d385a7813eb342418714a4c4fd4c04b9d7e2477e7a2208102ef536df09b79b84b8f3c41e8e5708", +}; + +export const testEventJSON = `{"id":"c7a702e6158744ca03508bbb4c90f9dbb0d6e88fefbfaa511d5ab24b4e3c48ad","pubkey":"cfa87f35acbde29ba1ab3ee42de527b2cad33ac487e80cf2d6405ea0042c8fef","created_at":1760740551,"kind":1,"tags":[],"content":"hello world","sig":"0fb7c1eaa867c4d16000587f2fb26c0b67e7e069f35d1acbb2d385a7813eb342418714a4c4fd4c04b9d7e2477e7a2208102ef536df09b79b84b8f3c41e8e5708"}`;