1import * as secp from '@noble/secp256k1'
3// The peer's identity is a secp256k1 / BIP340 (schnorr) keypair.
4// - The x-only public key (hex) IS the peer ID.
5// - The private key is persisted in localStorage so the identity survives reloads.
6// - The key signs the nostr events used for presence and WebRTC signaling, so
7// nobody can speak on behalf of another peer ID.
9const STORAGE_KEY = 'commonroom:privkey'
11const toHex = (bytes: Uint8Array): string =>
12 bytes.reduce((s, b) => s + b.toString(16).padStart(2, '0'), '')
14const fromHex = (hex: string): Uint8Array => {
15 const out = new Uint8Array(hex.length / 2)
16 for (let i = 0; i < out.length; i++) {
17 out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16)
18 }
19 return out
20}
22const loadOrCreateSecretKey = (): Uint8Array => {
23 const existing = localStorage.getItem(STORAGE_KEY)
24 if (existing && existing.length === 64) {
25 return fromHex(existing)
26 }
27 const {secretKey} = secp.schnorr.keygen()
28 localStorage.setItem(STORAGE_KEY, toHex(secretKey))
29 return secretKey
30}
32const secretKey = loadOrCreateSecretKey()
33const publicKey = secp.schnorr.getPublicKey(secretKey)
35/** This peer's ID = its x-only public key, as hex. */
36export const selfId: string = toHex(publicKey)
38const sha256 = async (str: string): Promise<Uint8Array> =>
39 new Uint8Array(
40 await crypto.subtle.digest('SHA-256', new TextEncoder().encode(str))
41 )
43// ---- nostr event signing (schnorr over the nostr event id) ----
45export interface NostrEvent {
46 id: string
47 pubkey: string
48 created_at: number
49 kind: number
50 tags: string[][]
51 content: string
52 sig: string
53}
55/** Build and sign a nostr event with this peer's key. */
56export const makeNostrEvent = async (
57 kind: number,
58 tags: string[][],
59 content: string
60): Promise<NostrEvent> => {
61 const created_at = Math.floor(Date.now() / 1000)
62 const serialized = JSON.stringify([
63 0,
64 selfId,
65 created_at,
66 kind,
67 tags,
68 content
69 ])
70 const id = toHex(await sha256(serialized))
71 const sig = toHex(await secp.schnorr.signAsync(fromHex(id), secretKey))
72 return {id, pubkey: selfId, created_at, kind, tags, content, sig}
73}
75export {toHex, fromHex}