bech32.mx raw

   1  package helpers
   2  
   3  // Bech32 encoding/decoding for NIP-19.
   4  // Implements bech32 (BIP-173) without external deps.
   5  
   6  const bech32Charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
   7  
   8  // Bech32Encode encodes data with the given human-readable part.
   9  func Bech32Encode(hrp string, data []byte) string {
  10  	values := bytesToBase32(data)
  11  	checksum := bech32Checksum(hrp, values)
  12  	values = append(values, checksum...)
  13  
  14  	buf := []byte{:0:len(hrp)+1+len(values)}
  15  	buf = append(buf, hrp...)
  16  	buf = append(buf, '1')
  17  	for _, v := range values {
  18  		buf = append(buf, bech32Charset[v])
  19  	}
  20  	return string(buf)
  21  }
  22  
  23  // Bech32Decode decodes a bech32 string. Returns hrp and data bytes.
  24  func Bech32Decode(s string) (string, []byte) {
  25  	// Find separator.
  26  	pos := -1
  27  	for i := len(s) - 1; i >= 0; i-- {
  28  		if s[i] == '1' {
  29  			pos = i
  30  			break
  31  		}
  32  	}
  33  	if pos < 1 || pos+7 > len(s) {
  34  		return "", nil
  35  	}
  36  
  37  	hrp := s[:pos]
  38  	dataStr := s[pos+1:]
  39  
  40  	values := []byte{:len(dataStr)}
  41  	for i := 0; i < len(dataStr); i++ {
  42  		idx := charsetIndex(dataStr[i])
  43  		if idx < 0 {
  44  			return "", nil
  45  		}
  46  		values[i] = byte(idx)
  47  	}
  48  
  49  	if !bech32Verify(hrp, values) {
  50  		return "", nil
  51  	}
  52  
  53  	// Strip checksum (last 6 chars).
  54  	values = values[:len(values)-6]
  55  	data := base32ToBytes(values)
  56  	return hrp, data
  57  }
  58  
  59  // NIP-19 helpers.
  60  
  61  // EncodeNpub encodes a 32-byte public key as npub.
  62  func EncodeNpub(pubkey []byte) string {
  63  	return Bech32Encode("npub", pubkey)
  64  }
  65  
  66  // EncodeNsec encodes a 32-byte secret key as nsec.
  67  func EncodeNsec(seckey []byte) string {
  68  	return Bech32Encode("nsec", seckey)
  69  }
  70  
  71  // EncodeNote encodes a 32-byte event ID as note.
  72  func EncodeNote(eventID []byte) string {
  73  	return Bech32Encode("note", eventID)
  74  }
  75  
  76  // EncodeNevent encodes an event reference as nevent (NIP-19 TLV).
  77  func EncodeNevent(id string, relays []string, author string) string {
  78  	var data []byte
  79  	idBytes := HexDecode(id)
  80  	if len(idBytes) == 32 {
  81  		data = append(data, 0, 32)
  82  		data = append(data, idBytes...)
  83  	}
  84  	for _, r := range relays {
  85  		rb := []byte(r)
  86  		data = append(data, 1, byte(len(rb)))
  87  		data = append(data, rb...)
  88  	}
  89  	if author != "" {
  90  		ab := HexDecode(author)
  91  		if len(ab) == 32 {
  92  			data = append(data, 2, 32)
  93  			data = append(data, ab...)
  94  		}
  95  	}
  96  	return Bech32Encode("nevent", data)
  97  }
  98  
  99  // DecodeNpub decodes an npub string to 32 bytes.
 100  func DecodeNpub(s string) []byte {
 101  	hrp, data := Bech32Decode(s)
 102  	if hrp != "npub" || len(data) != 32 {
 103  		return nil
 104  	}
 105  	return data
 106  }
 107  
 108  // DecodeNsec decodes an nsec string to 32 bytes.
 109  func DecodeNsec(s string) []byte {
 110  	hrp, data := Bech32Decode(s)
 111  	if hrp != "nsec" || len(data) != 32 {
 112  		return nil
 113  	}
 114  	return data
 115  }
 116  
 117  // DecodeNote decodes a note string to 32 bytes.
 118  func DecodeNote(s string) []byte {
 119  	hrp, data := Bech32Decode(s)
 120  	if hrp != "note" || len(data) != 32 {
 121  		return nil
 122  	}
 123  	return data
 124  }
 125  
 126  // Nevent holds decoded nevent TLV data (NIP-19).
 127  type Nevent struct {
 128  	ID     string   // hex event ID
 129  	Relays []string // optional relay hints
 130  	Author string   // hex pubkey (optional)
 131  }
 132  
 133  // DecodeNevent decodes a nevent1... bech32 string (TLV format).
 134  func DecodeNevent(s string) *Nevent {
 135  	hrp, data := Bech32Decode(s)
 136  	if hrp != "nevent" {
 137  		return nil
 138  	}
 139  	result := &Nevent{}
 140  	i := 0
 141  	for i+2 <= len(data) {
 142  		t := data[i]
 143  		l := int(data[i+1])
 144  		i += 2
 145  		if i+l > len(data) {
 146  			break
 147  		}
 148  		v := data[i : i+l]
 149  		i += l
 150  		switch t {
 151  		case 0:
 152  			if l == 32 {
 153  				result.ID = HexEncode(v)
 154  			}
 155  		case 1:
 156  			result.Relays = append(result.Relays, string(v))
 157  		case 2:
 158  			if l == 32 {
 159  				result.Author = HexEncode(v)
 160  			}
 161  		}
 162  	}
 163  	if result.ID == "" {
 164  		return nil
 165  	}
 166  	return result
 167  }
 168  
 169  // Nprofile holds decoded nprofile TLV data (NIP-19).
 170  type Nprofile struct {
 171  	Pubkey string   // hex pubkey
 172  	Relays []string // optional relay hints
 173  }
 174  
 175  // DecodeNprofile decodes an nprofile1... bech32 string (TLV format).
 176  func DecodeNprofile(s string) *Nprofile {
 177  	hrp, data := Bech32Decode(s)
 178  	if hrp != "nprofile" {
 179  		return nil
 180  	}
 181  	result := &Nprofile{}
 182  	i := 0
 183  	for i+2 <= len(data) {
 184  		t := data[i]
 185  		l := int(data[i+1])
 186  		i += 2
 187  		if i+l > len(data) {
 188  			break
 189  		}
 190  		v := data[i : i+l]
 191  		i += l
 192  		switch t {
 193  		case 0:
 194  			if l == 32 {
 195  				result.Pubkey = HexEncode(v)
 196  			}
 197  		case 1:
 198  			result.Relays = append(result.Relays, string(v))
 199  		}
 200  	}
 201  	if result.Pubkey == "" {
 202  		return nil
 203  	}
 204  	return result
 205  }
 206  
 207  // PubkeyShort returns first 8 chars of hex pubkey.
 208  func PubkeyShort(pubkey string) string {
 209  	if len(pubkey) >= 8 {
 210  		return pubkey[:8]
 211  	}
 212  	return pubkey
 213  }
 214  
 215  // Internal bech32 functions.
 216  
 217  func bytesToBase32(data []byte) []byte {
 218  	var out []byte
 219  	acc := 0
 220  	bits := 0
 221  	for _, b := range data {
 222  		acc = (acc << 8) | int(b)
 223  		bits += 8
 224  		for bits >= 5 {
 225  			bits -= 5
 226  			out = append(out, byte((acc>>bits)&0x1f))
 227  		}
 228  		acc &= (1 << uint(bits)) - 1
 229  	}
 230  	if bits > 0 {
 231  		out = append(out, byte((acc<<(5-bits))&0x1f))
 232  	}
 233  	return out
 234  }
 235  
 236  func base32ToBytes(data []byte) []byte {
 237  	var out []byte
 238  	acc := 0
 239  	bits := 0
 240  	for _, v := range data {
 241  		acc = (acc << 5) | int(v)
 242  		bits += 5
 243  		for bits >= 8 {
 244  			bits -= 8
 245  			out = append(out, byte((acc>>bits)&0xff))
 246  		}
 247  		acc &= (1 << uint(bits)) - 1
 248  	}
 249  	return out
 250  }
 251  
 252  func bech32Polymod(values []byte) uint32 {
 253  	gen := [5]uint32{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}
 254  	chk := uint32(1)
 255  	for _, v := range values {
 256  		b := chk >> 25
 257  		chk = ((chk & 0x1ffffff) << 5) ^ uint32(v)
 258  		for i := 0; i < 5; i++ {
 259  			if (b>>uint(i))&1 == 1 {
 260  				chk ^= gen[i]
 261  			}
 262  		}
 263  	}
 264  	return chk
 265  }
 266  
 267  func bech32HRPExpand(hrp string) []byte {
 268  	out := []byte{:0:len(hrp)*2+1}
 269  	for i := 0; i < len(hrp); i++ {
 270  		out = append(out, byte(hrp[i]>>5))
 271  	}
 272  	out = append(out, 0)
 273  	for i := 0; i < len(hrp); i++ {
 274  		out = append(out, byte(hrp[i]&0x1f))
 275  	}
 276  	return out
 277  }
 278  
 279  func bech32Checksum(hrp string, data []byte) []byte {
 280  	values := append(bech32HRPExpand(hrp), data...)
 281  	values = append(values, 0, 0, 0, 0, 0, 0)
 282  	polymod := bech32Polymod(values) ^ 1
 283  	out := []byte{:6}
 284  	for i := 0; i < 6; i++ {
 285  		out[i] = byte((polymod >> (5 * (5 - uint(i)))) & 0x1f)
 286  	}
 287  	return out
 288  }
 289  
 290  func bech32Verify(hrp string, data []byte) bool {
 291  	values := append(bech32HRPExpand(hrp), data...)
 292  	return bech32Polymod(values) == 1
 293  }
 294  
 295  func charsetIndex(c byte) int {
 296  	for i := 0; i < len(bech32Charset); i++ {
 297  		if bech32Charset[i] == c {
 298  			return i
 299  		}
 300  	}
 301  	return -1
 302  }
 303