hex.go raw

   1  // Package nostr implements the NIP-01 data model: events, tags, filters and
   2  // their canonical JSON encoding.
   3  //
   4  // The canonical form matters: an event ID is the SHA-256 of
   5  //
   6  //	[0,"<pubkey>",<created_at>,<kind>,<tags>,"<content>"]
   7  //
   8  // serialized with the same escaping rules as JavaScript's JSON.stringify.
   9  // Go's encoding/json escapes HTML characters by default, which would produce a
  10  // different hash, so this package carries its own string encoder.
  11  package nostr
  12  
  13  import (
  14  	"encoding/hex"
  15  	"errors"
  16  )
  17  
  18  // Field sizes in bytes.
  19  const (
  20  	IDLen  = 32
  21  	PubLen = 32
  22  	SigLen = 64
  23  
  24  	// MaxKind is the largest kind representable in the store's two-byte index.
  25  	MaxKind = 65535
  26  )
  27  
  28  var (
  29  	ErrBadID      = errors.New("nostr: malformed id")
  30  	ErrBadPubKey  = errors.New("nostr: malformed pubkey")
  31  	ErrBadSig     = errors.New("nostr: malformed signature")
  32  	ErrBadKind    = errors.New("nostr: kind out of range")
  33  	ErrBadCreated = errors.New("nostr: malformed created_at")
  34  )
  35  
  36  // Decode32 decodes a 64-character lowercase hex string into dst.
  37  // Uppercase hex is rejected: NIP-01 defines these as lowercase, and the event
  38  // ID is computed over the lowercase encoding, so accepting uppercase would
  39  // make an event that verifies under one spelling fail under another.
  40  func Decode32(s string, dst []byte) error {
  41  	if len(s) != 64 {
  42  		return ErrBadID
  43  	}
  44  	for i := 0; i < 64; i++ {
  45  		c := s[i]
  46  		if !(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') {
  47  			return ErrBadID
  48  		}
  49  	}
  50  	_, err := hex.Decode(dst, []byte(s))
  51  	return err
  52  }
  53  
  54  // DecodeRaw decodes a lowercase hex string of the given byte length.
  55  func DecodeRaw(s string, dst []byte) error {
  56  	if len(s) != len(dst)*2 {
  57  		return errors.New("nostr: wrong hex length")
  58  	}
  59  	for i := 0; i < len(s); i++ {
  60  		c := s[i]
  61  		if !(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') {
  62  			return errors.New("nostr: non-lowercase hex")
  63  		}
  64  	}
  65  	_, err := hex.Decode(dst, []byte(s))
  66  	return err
  67  }
  68  
  69  // Hex returns the lowercase hex encoding of b.
  70  func Hex(b []byte) string {
  71  	return hex.EncodeToString(b)
  72  }
  73  
  74  // IsHex64 reports whether s is 64 lowercase hex characters.
  75  func IsHex64(s string) bool {
  76  	if len(s) != 64 {
  77  		return false
  78  	}
  79  	for i := 0; i < len(s); i++ {
  80  		c := s[i]
  81  		if !(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') {
  82  			return false
  83  		}
  84  	}
  85  	return true
  86  }
  87  
  88  // AppendHex appends the lowercase hex encoding of src to dst.
  89  func AppendHex(dst, src []byte) []byte {
  90  	const digits = "0123456789abcdef"
  91  	for _, b := range src {
  92  		dst = append(dst, digits[b>>4], digits[b&0x0f])
  93  	}
  94  	return dst
  95  }
  96