// Package sig adds BIP-340 Schnorr signature operations to nostr events. // Kept separate from nostr so the base nostr package carries no secp256k1 dep. // Only binaries that sign or verify import this package. // // Event is nostr.Event with the same underlying struct — zero-cost cast in both // directions. Call site: // (*sig.Event)(ev).CheckSig() // (*sig.Event)(ev).Sign(seckey, auxRand) package sig import ( "crypto/sha256" "smesh.lol/web/common/helpers" "smesh.lol/web/common/jsbridge/schnorr" "smesh.lol/web/common/nostr" ) // Event is the same struct as nostr.Event; the type definition carries the // signature and hash methods. (*Event)(ev) is a zero-cost cast: same pointer, // same layout. type Event nostr.Event // ComputeID computes the SHA-256 event ID from the serialized event and stores // it in e.ID. Returns the hex ID string. func (e *Event) ComputeID() string { ser := (*nostr.Event)(e).Serialize() hash := sha256.Sum([]byte(ser)) e.ID = helpers.HexEncode(hash[:]) return e.ID } // CheckID returns true if e.ID matches the SHA-256 of the canonical serialization. func (e *Event) CheckID() bool { ser := (*nostr.Event)(e).Serialize() hash := sha256.Sum([]byte(ser)) return e.ID == helpers.HexEncode(hash[:]) } // CheckSig returns true if the BIP-340 Schnorr signature in e.Sig is valid // for e.ID signed by the key in e.PubKey. func (e *Event) CheckSig() bool { pk := helpers.HexDecode(e.PubKey) if len(pk) != 32 { return false } id := helpers.HexDecode(e.ID) if len(id) != 32 { return false } if len(e.Sig) != 128 { return false } sig := helpers.HexDecode(e.Sig) if len(sig) != 64 { return false } return schnorr.VerifySchnorr(pk, id, sig) } // Sign derives e.PubKey from seckey, computes e.ID, signs it, and sets e.Sig. // seckey and auxRand are zeroed before returning. func (e *Event) Sign(seckey [32]byte, auxRand [32]byte) bool { pk, ok := schnorr.PubKeyFromSecKey(seckey[:]) if !ok { clear(seckey[:]) clear(auxRand[:]) return false } e.PubKey = helpers.HexEncode(pk) e.ComputeID() idBytes := helpers.HexDecode(e.ID) if len(idBytes) != 32 { clear(seckey[:]) clear(auxRand[:]) return false } sigBytes, ok := schnorr.SignSchnorr(seckey[:], idBytes, auxRand[:]) clear(seckey[:]) clear(auxRand[:]) if !ok { return false } e.Sig = helpers.HexEncode(sigBytes) return true }