nip19.go raw
1 package bech32encoding
2
3 import (
4 "bytes"
5 "encoding/binary"
6
7 "next.orly.dev/pkg/nostr/crypto/ec/bech32"
8 "next.orly.dev/pkg/nostr/crypto/ec/schnorr"
9 "next.orly.dev/pkg/nostr/encoders/bech32encoding/pointers"
10 "next.orly.dev/pkg/nostr/encoders/bech32encoding/tlv"
11 "next.orly.dev/pkg/nostr/encoders/hex"
12 "next.orly.dev/pkg/nostr/encoders/kind"
13 "next.orly.dev/pkg/nostr/utils"
14 "github.com/minio/sha256-simd"
15 "next.orly.dev/pkg/lol/chk"
16 "next.orly.dev/pkg/lol/errorf"
17 "next.orly.dev/pkg/lol/log"
18 )
19
20 var (
21 // NoteHRP is the Human Readable Prefix (HRP) for a nostr note (kind 1)
22 NoteHRP = []byte("note")
23
24 // NsecHRP is the Human Readable Prefix (HRP) for a nostr secret key
25 NsecHRP = []byte("nsec")
26
27 // NpubHRP is the Human Readable Prefix (HRP) for a nostr public key
28 NpubHRP = []byte("npub")
29
30 // NprofileHRP is the Human Readable Prefix (HRP) for a nostr profile metadata
31 // event (kind 0)
32 NprofileHRP = []byte("nprofile")
33
34 // NeventHRP is the Human Readable Prefix (HRP) for a nostr event, which may
35 // include relay hints to find the event, and the author's npub.
36 NeventHRP = []byte("nevent")
37
38 // NentityHRP is the Human Readable Prefix (HRP) for a nostr is a generic nostr
39 // entity, which may include relay hints to find the event, and the author's
40 // npub.
41 NentityHRP = []byte("naddr")
42 )
43
44 // Decode a nostr bech32 encoded entity, return the prefix, and the decoded
45 // value, and any error if one occurred in the process of decoding.
46 func Decode(bech32string []byte) (prefix []byte, value any, err error) {
47 var bits5 []byte
48 if prefix, bits5, err = bech32.DecodeNoLimit(bech32string); chk.D(err) {
49 return
50 }
51 var data []byte
52 if data, err = bech32.ConvertBits(bits5, 5, 8, false); chk.D(err) {
53 return prefix, nil, errorf.E(
54 "failed translating data into 8 bits: %s", err.Error(),
55 )
56 }
57 buf := bytes.NewBuffer(data)
58 switch {
59 case utils.FastEqual(prefix, NpubHRP) ||
60 utils.FastEqual(prefix, NsecHRP) ||
61 utils.FastEqual(prefix, NoteHRP):
62 if len(data) < 32 {
63 return prefix, nil, errorf.E(
64 "data is less than 32 bytes (%d)", len(data),
65 )
66 }
67 b := make([]byte, schnorr.PubKeyBytesLen*2)
68 hex.EncBytes(b, data[:32])
69 return prefix, b, nil
70 case utils.FastEqual(prefix, NprofileHRP):
71 var result pointers.Profile
72 for {
73 t, v := tlv.ReadEntry(buf)
74 if len(v) == 0 {
75 // end here
76 if len(result.PublicKey) < 1 {
77 return prefix, result, errorf.E("no pubkey found for nprofile")
78 }
79 return prefix, result, nil
80 }
81 switch t {
82 case tlv.Default:
83 if len(v) < 32 {
84 return prefix, nil, errorf.E(
85 "pubkey is less than 32 bytes (%d)", len(v),
86 )
87 }
88 result.PublicKey = make([]byte, schnorr.PubKeyBytesLen*2)
89 hex.EncBytes(result.PublicKey, v)
90 case tlv.Relay:
91 result.Relays = append(result.Relays, v)
92 default:
93 // ignore
94 }
95 }
96 case utils.FastEqual(prefix, NeventHRP):
97 var result pointers.Event
98 for {
99 t, v := tlv.ReadEntry(buf)
100 if v == nil {
101 // end here
102 if len(result.ID) == 0 {
103 return prefix, result, errorf.E("no id found for nevent")
104 }
105 return prefix, result, nil
106 }
107 switch t {
108 case tlv.Default:
109 if len(v) < 32 {
110 return prefix, nil, errorf.E(
111 "id is less than 32 bytes (%d)", len(v),
112 )
113 }
114 result.ID = v
115 case tlv.Relay:
116 result.Relays = append(result.Relays, v)
117 case tlv.Author:
118 if len(v) < 32 {
119 return prefix, nil, errorf.E(
120 "author is less than 32 bytes (%d)", len(v),
121 )
122 }
123 result.Author = make([]byte, schnorr.PubKeyBytesLen*2)
124 hex.EncBytes(result.Author, v)
125 case tlv.Kind:
126 result.Kind = kind.New(binary.BigEndian.Uint32(v))
127 default:
128 // ignore
129 }
130 }
131 case utils.FastEqual(prefix, NentityHRP):
132 var result pointers.Entity
133 for {
134 t, v := tlv.ReadEntry(buf)
135 if v == nil {
136 // end here
137 if result.Kind.ToU16() == 0 ||
138 len(result.Identifier) < 1 ||
139 len(result.PublicKey) < 1 {
140
141 return prefix, result, errorf.E("incomplete naddr")
142 }
143 return prefix, result, nil
144 }
145 switch t {
146 case tlv.Default:
147 result.Identifier = v
148 case tlv.Relay:
149 result.Relays = append(result.Relays, v)
150 case tlv.Author:
151 if len(v) < 32 {
152 return prefix, nil, errorf.E(
153 "author is less than 32 bytes (%d)", len(v),
154 )
155 }
156 result.PublicKey = make([]byte, schnorr.PubKeyBytesLen*2)
157 hex.EncBytes(result.PublicKey, v)
158 case tlv.Kind:
159 result.Kind = kind.New(binary.BigEndian.Uint32(v))
160 default:
161 log.D.Ln("got a bogus TLV type code", t)
162 // ignore
163 }
164 }
165 }
166 return prefix, data, errorf.E("unknown tag %s", prefix)
167 }
168
169 // EncodeNote encodes a standard nostr NIP-19 note entity (mostly meaning a
170 // nostr kind 1 short text note)
171 func EncodeNote(eventIDHex []byte) (s []byte, err error) {
172 var b []byte
173 if _, err = hex.DecBytes(b, eventIDHex); chk.D(err) {
174 err = log.E.Err("failed to decode event id hex: %w", err)
175 return
176 }
177 var bits5 []byte
178 if bits5, err = bech32.ConvertBits(b, 8, 5, true); chk.D(err) {
179 return
180 }
181 return bech32.Encode(NoteHRP, bits5)
182 }
183
184 // EncodeProfile encodes a pubkey and a set of relays into a bech32 encoded
185 // entity.
186 func EncodeProfile(publicKeyHex []byte, relays [][]byte) (s []byte, err error) {
187 buf := &bytes.Buffer{}
188 pb := make([]byte, schnorr.PubKeyBytesLen)
189 if _, err = hex.DecBytes(pb, publicKeyHex); chk.D(err) {
190 err = log.E.Err("invalid pubkey '%s': %w", publicKeyHex, err)
191 return
192 }
193 tlv.WriteEntry(buf, tlv.Default, pb)
194 for _, url := range relays {
195 tlv.WriteEntry(buf, tlv.Relay, []byte(url))
196 }
197 var bits5 []byte
198 if bits5, err = bech32.ConvertBits(buf.Bytes(), 8, 5, true); chk.D(err) {
199 err = log.E.Err("failed to convert bits: %w", err)
200 return
201 }
202 return bech32.Encode(NprofileHRP, bits5)
203 }
204
205 // EncodeEvent encodes an event, including relay hints and author pubkey.
206 func EncodeEvent(
207 eventIDHex []byte, relays [][]byte, author []byte,
208 ) (s []byte, err error) {
209 buf := &bytes.Buffer{}
210 id := make([]byte, sha256.Size)
211 if _, err = hex.DecBytes(id, eventIDHex); chk.D(err) ||
212 len(id) != 32 {
213 return nil, errorf.E(
214 "invalid id %d '%s': %v", len(id), eventIDHex,
215 err,
216 )
217 }
218 tlv.WriteEntry(buf, tlv.Default, id)
219 for _, url := range relays {
220 tlv.WriteEntry(buf, tlv.Relay, []byte(url))
221 }
222 pubkey := make([]byte, schnorr.PubKeyBytesLen)
223 if _, err = hex.DecBytes(pubkey, author); len(pubkey) == 32 {
224 tlv.WriteEntry(buf, tlv.Author, pubkey)
225 }
226 var bits5 []byte
227 if bits5, err = bech32.ConvertBits(buf.Bytes(), 8, 5, true); chk.D(err) {
228 err = log.E.Err("failed to convert bits: %w", err)
229 return
230 }
231 return bech32.Encode(NeventHRP, bits5)
232 }
233
234 // EncodeEntity encodes a pubkey, kind, event ID, and relay hints.
235 func EncodeEntity(pk []byte, k *kind.K, id []byte, relays [][]byte) (
236 s []byte, err error,
237 ) {
238 buf := &bytes.Buffer{}
239 tlv.WriteEntry(buf, tlv.Default, []byte(id))
240 for _, url := range relays {
241 tlv.WriteEntry(buf, tlv.Relay, []byte(url))
242 }
243 pb := make([]byte, schnorr.PubKeyBytesLen)
244 if _, err = hex.DecBytes(pb, pk); chk.D(err) {
245 return nil, errorf.E("invalid pubkey '%s': %w", pb, err)
246 }
247 tlv.WriteEntry(buf, tlv.Author, pb)
248 kindBytes := make([]byte, 4)
249 binary.BigEndian.PutUint32(kindBytes, uint32(k.K))
250 tlv.WriteEntry(buf, tlv.Kind, kindBytes)
251 var bits5 []byte
252 if bits5, err = bech32.ConvertBits(buf.Bytes(), 8, 5, true); chk.D(err) {
253 return nil, errorf.E("failed to convert bits: %w", err)
254 }
255 return bech32.Encode(NentityHRP, bits5)
256 }
257