// Package nostr implements the NIP-01 data model: events, tags, filters and // their canonical JSON encoding. // // The canonical form matters: an event ID is the SHA-256 of // // [0,"",,,,""] // // serialized with the same escaping rules as JavaScript's JSON.stringify. // Go's encoding/json escapes HTML characters by default, which would produce a // different hash, so this package carries its own string encoder. package nostr import ( "encoding/hex" "errors" ) // Field sizes in bytes. const ( IDLen = 32 PubLen = 32 SigLen = 64 // MaxKind is the largest kind representable in the store's two-byte index. MaxKind = 65535 ) var ( ErrBadID = errors.New("nostr: malformed id") ErrBadPubKey = errors.New("nostr: malformed pubkey") ErrBadSig = errors.New("nostr: malformed signature") ErrBadKind = errors.New("nostr: kind out of range") ErrBadCreated = errors.New("nostr: malformed created_at") ) // Decode32 decodes a 64-character lowercase hex string into dst. // Uppercase hex is rejected: NIP-01 defines these as lowercase, and the event // ID is computed over the lowercase encoding, so accepting uppercase would // make an event that verifies under one spelling fail under another. func Decode32(s string, dst []byte) error { if len(s) != 64 { return ErrBadID } for i := 0; i < 64; i++ { c := s[i] if !(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') { return ErrBadID } } _, err := hex.Decode(dst, []byte(s)) return err } // DecodeRaw decodes a lowercase hex string of the given byte length. func DecodeRaw(s string, dst []byte) error { if len(s) != len(dst)*2 { return errors.New("nostr: wrong hex length") } for i := 0; i < len(s); i++ { c := s[i] if !(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') { return errors.New("nostr: non-lowercase hex") } } _, err := hex.Decode(dst, []byte(s)) return err } // Hex returns the lowercase hex encoding of b. func Hex(b []byte) string { return hex.EncodeToString(b) } // IsHex64 reports whether s is 64 lowercase hex characters. func IsHex64(s string) bool { if len(s) != 64 { return false } for i := 0; i < len(s); i++ { c := s[i] if !(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') { return false } } return true } // AppendHex appends the lowercase hex encoding of src to dst. func AppendHex(dst, src []byte) []byte { const digits = "0123456789abcdef" for _, b := range src { dst = append(dst, digits[b>>4], digits[b&0x0f]) } return dst }