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