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