200df8aRecord commonroom audio and chat from the command lineJeremy Magland 1import * as secp from '@noble/secp256k1'
3// Ported from commonroom's identity.ts. The peer identity is a secp256k1 /
4// BIP340 (schnorr) keypair; the x-only public key (hex) IS the peer ID, and it
5// signs every nostr event so nobody can speak on behalf of another peer.
6//
7// One deliberate change from the browser client: the key is EPHEMERAL — a
8// fresh identity per run, nothing persisted. A recorder bot has no reason to
9// keep a stable identity, and a fresh key sidesteps stale-presence clashes
10// when a previous run died uncleanly.
12const toHex = (bytes: Uint8Array): string =>
13 bytes.reduce((s, b) => s + b.toString(16).padStart(2, '0'), '')
15const fromHex = (hex: string): Uint8Array => {
16 const out = new Uint8Array(hex.length / 2)
17 for (let i = 0; i < out.length; i++) {
18 out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16)
19 }
20 return out
21}
23const {secretKey} = secp.schnorr.keygen()
24const publicKey = secp.schnorr.getPublicKey(secretKey)
26/** This peer's ID = its x-only public key, as hex. */
27export const selfId: string = toHex(publicKey)
29const sha256 = async (str: string): Promise<Uint8Array> =>
30 new Uint8Array(
31 await crypto.subtle.digest('SHA-256', new TextEncoder().encode(str))
32 )
34// ---- nostr event signing (schnorr over the nostr event id) ----
36export interface NostrEvent {
37 id: string
38 pubkey: string
39 created_at: number
40 kind: number
41 tags: string[][]
42 content: string
43 sig: string
44}
46/** Build and sign a nostr event with this peer's key. */
47export const makeNostrEvent = async (
48 kind: number,
49 tags: string[][],
50 content: string
51): Promise<NostrEvent> => {
52 const created_at = Math.floor(Date.now() / 1000)
53 const serialized = JSON.stringify([
54 0,
55 selfId,
56 created_at,
57 kind,
58 tags,
59 content
60 ])
61 const id = toHex(await sha256(serialized))
62 const sig = toHex(await secp.schnorr.signAsync(fromHex(id), secretKey))
63 return {id, pubkey: selfId, created_at, kind, tags, content, sig}
64}
66export {toHex, fromHex}