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