sig.mx raw
1 // Package sig adds BIP-340 Schnorr signature operations to nostr events.
2 // Kept separate from nostr so the base nostr package carries no secp256k1 dep.
3 // Only binaries that sign or verify import this package.
4 //
5 // Event is nostr.Event with the same underlying struct — zero-cost cast in both
6 // directions. Call site:
7 // (*sig.Event)(ev).CheckSig()
8 // (*sig.Event)(ev).Sign(seckey, auxRand)
9 package sig
10
11 import (
12 "crypto/sha256"
13 "smesh.lol/web/common/helpers"
14 "smesh.lol/web/common/jsbridge/schnorr"
15 "smesh.lol/web/common/nostr"
16 )
17
18 // Event is the same struct as nostr.Event; the type definition carries the
19 // signature and hash methods. (*Event)(ev) is a zero-cost cast: same pointer,
20 // same layout.
21 type Event nostr.Event
22
23 // ComputeID computes the SHA-256 event ID from the serialized event and stores
24 // it in e.ID. Returns the hex ID string.
25 func (e *Event) ComputeID() string {
26 ser := (*nostr.Event)(e).Serialize()
27 hash := sha256.Sum([]byte(ser))
28 e.ID = helpers.HexEncode(hash[:])
29 return e.ID
30 }
31
32 // CheckID returns true if e.ID matches the SHA-256 of the canonical serialization.
33 func (e *Event) CheckID() bool {
34 ser := (*nostr.Event)(e).Serialize()
35 hash := sha256.Sum([]byte(ser))
36 return e.ID == helpers.HexEncode(hash[:])
37 }
38
39 // CheckSig returns true if the BIP-340 Schnorr signature in e.Sig is valid
40 // for e.ID signed by the key in e.PubKey.
41 func (e *Event) CheckSig() bool {
42 pk := helpers.HexDecode(e.PubKey)
43 if len(pk) != 32 {
44 return false
45 }
46 id := helpers.HexDecode(e.ID)
47 if len(id) != 32 {
48 return false
49 }
50 if len(e.Sig) != 128 {
51 return false
52 }
53 sig := helpers.HexDecode(e.Sig)
54 if len(sig) != 64 {
55 return false
56 }
57 return schnorr.VerifySchnorr(pk, id, sig)
58 }
59
60 // Sign derives e.PubKey from seckey, computes e.ID, signs it, and sets e.Sig.
61 // seckey and auxRand are zeroed before returning.
62 func (e *Event) Sign(seckey [32]byte, auxRand [32]byte) bool {
63 pk, ok := schnorr.PubKeyFromSecKey(seckey[:])
64 if !ok {
65 clear(seckey[:])
66 clear(auxRand[:])
67 return false
68 }
69 e.PubKey = helpers.HexEncode(pk)
70 e.ComputeID()
71 idBytes := helpers.HexDecode(e.ID)
72 if len(idBytes) != 32 {
73 clear(seckey[:])
74 clear(auxRand[:])
75 return false
76 }
77 sigBytes, ok := schnorr.SignSchnorr(seckey[:], idBytes, auxRand[:])
78 clear(seckey[:])
79 clear(auxRand[:])
80 if !ok {
81 return false
82 }
83 e.Sig = helpers.HexEncode(sigBytes)
84 return true
85 }
86