bech32.mx raw

   1  package helpers
   2  
   3  import "git.smesh.lol/moxie/pkg/mxutil"
   4  
   5  // Bech32 encoding/decoding for NIP-19.
   6  // Implements bech32 (BIP-173) without external deps.
   7  
   8  const bech32Charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
   9  
  10  // Bech32Encode encodes data with the given human-readable part. The output
  11  // size is known once the checksum exists, so it is presized and written
  12  // through a cursor: `buf | "1"` had a non-empty left side, and concat then
  13  // allocated a fresh exact-size buffer, leaving the first value push no room.
  14  func Bech32Encode(hrp string, data []byte) (s string) {
  15  	values := bytesToBase32(data)
  16  	checksum := bech32Checksum(hrp, values)
  17  	values = values | checksum
  18  
  19  	buf := []byte{:len(hrp) + 1 + len(values)}
  20  	j := 0
  21  	for i := 0; i < len(hrp); i++ {
  22  		buf[j] = hrp[i]
  23  		j++
  24  	}
  25  	buf[j] = '1'
  26  	j++
  27  	for _, v := range values {
  28  		buf[j] = bech32Charset[v]
  29  		j++
  30  	}
  31  	return string(buf)
  32  }
  33  
  34  // Bech32Decode decodes a bech32 string. Returns hrp and data bytes.
  35  func Bech32Decode(s string) (hrp string, data []byte) {
  36  	// Find separator.
  37  	pos := -1
  38  	for i := len(s) - 1; i >= 0; i-- {
  39  		if s[i] == '1' {
  40  			pos = i
  41  			break
  42  		}
  43  	}
  44  	if pos < 1 || pos+7 > len(s) {
  45  		return "", nil
  46  	}
  47  
  48  	hrp := s[:pos]
  49  	dataStr := s[pos+1:]
  50  
  51  	values := []byte{:len(dataStr)}
  52  	for i := 0; i < len(dataStr); i++ {
  53  		idx := charsetIndex(dataStr[i])
  54  		if idx < 0 {
  55  			return "", nil
  56  		}
  57  		values[i] = byte(idx)
  58  	}
  59  
  60  	if !bech32Verify(hrp, values) {
  61  		return "", nil
  62  	}
  63  
  64  	// Strip checksum (last 6 chars).
  65  	values = values[:len(values)-6]
  66  	data := base32ToBytes(values)
  67  	return hrp, data
  68  }
  69  
  70  // NIP-19 helpers.
  71  
  72  // EncodeNpub encodes a 32-byte public key as npub.
  73  func EncodeNpub(pubkey []byte) (s string) {
  74  	return Bech32Encode("npub", pubkey)
  75  }
  76  
  77  // EncodeNsec encodes a 32-byte secret key as nsec.
  78  func EncodeNsec(seckey []byte) (s string) {
  79  	return Bech32Encode("nsec", seckey)
  80  }
  81  
  82  // EncodeNote encodes a 32-byte event ID as note.
  83  func EncodeNote(eventID []byte) (s string) {
  84  	return Bech32Encode("note", eventID)
  85  }
  86  
  87  // EncodeNevent encodes an event reference as nevent (NIP-19 TLV).
  88  // The TLV length is a function of its parts, so it is presized exactly: push
  89  // does not grow, and the `data | idBytes` step returns a full slice.
  90  func EncodeNevent(id string, relays []string, author string) (s string) {
  91  	idBytes := HexDecode(id)
  92  	ab := HexDecode(author)
  93  	hasID := len(idBytes) == 32
  94  	hasAuthor := len(ab) == 32
  95  	n := 0
  96  	if hasID {
  97  		n += 34
  98  	}
  99  	for _, r := range relays {
 100  		n += 2 + len(r)
 101  	}
 102  	if hasAuthor {
 103  		n += 34
 104  	}
 105  	data := []byte{:n}
 106  	j := 0
 107  	if hasID {
 108  		data[j] = 0
 109  		data[j+1] = 32
 110  		j += 2
 111  		for _, b := range idBytes {
 112  			data[j] = b
 113  			j++
 114  		}
 115  	}
 116  	for _, r := range relays {
 117  		data[j] = 1
 118  		data[j+1] = byte(len(r))
 119  		j += 2
 120  		for k := 0; k < len(r); k++ {
 121  			data[j] = r[k]
 122  			j++
 123  		}
 124  	}
 125  	if hasAuthor {
 126  		data[j] = 2
 127  		data[j+1] = 32
 128  		j += 2
 129  		for _, b := range ab {
 130  			data[j] = b
 131  			j++
 132  		}
 133  	}
 134  	return Bech32Encode("nevent", data[:j])
 135  }
 136  
 137  // DecodeNpub decodes an npub string to 32 bytes.
 138  func DecodeNpub(s string) (buf []byte) {
 139  	hrp, data := Bech32Decode(s)
 140  	if hrp != "npub" || len(data) != 32 {
 141  		return nil
 142  	}
 143  	return data
 144  }
 145  
 146  // DecodeNsec decodes an nsec string to 32 bytes.
 147  func DecodeNsec(s string) (buf []byte) {
 148  	hrp, data := Bech32Decode(s)
 149  	if hrp != "nsec" || len(data) != 32 {
 150  		return nil
 151  	}
 152  	return data
 153  }
 154  
 155  // DecodeNote decodes a note string to 32 bytes.
 156  func DecodeNote(s string) (buf []byte) {
 157  	hrp, data := Bech32Decode(s)
 158  	if hrp != "note" || len(data) != 32 {
 159  		return nil
 160  	}
 161  	return data
 162  }
 163  
 164  // Nevent holds decoded nevent TLV data (NIP-19).
 165  type Nevent struct {
 166  	ID     string   // hex event ID
 167  	Relays []string // optional relay hints
 168  	Author string   // hex pubkey (optional)
 169  }
 170  
 171  // DecodeNevent decodes a nevent1... bech32 string (TLV format).
 172  func DecodeNevent(s string) (n *Nevent) {
 173  	hrp, data := Bech32Decode(s)
 174  	if hrp != "nevent" {
 175  		return nil
 176  	}
 177  	result := &Nevent{}
 178  	i := 0
 179  	for i+2 <= len(data) {
 180  		t := data[i]
 181  		l := int32(data[i+1])
 182  		i += 2
 183  		if i+l > len(data) {
 184  			break
 185  		}
 186  		v := data[i : i+l]
 187  		i += l
 188  		switch t {
 189  		case 0:
 190  			if l == 32 {
 191  				result.ID = HexEncode(v)
 192  			}
 193  		case 1:
 194  			result.Relays = mxutil.Ensure(result.Relays, 1)
 195  			result.Relays = push(result.Relays, string(v))
 196  		case 2:
 197  			if l == 32 {
 198  				result.Author = HexEncode(v)
 199  			}
 200  		}
 201  	}
 202  	if result.ID == "" {
 203  		return nil
 204  	}
 205  	return result
 206  }
 207  
 208  // Nprofile holds decoded nprofile TLV data (NIP-19).
 209  type Nprofile struct {
 210  	Pubkey string   // hex pubkey
 211  	Relays []string // optional relay hints
 212  }
 213  
 214  // DecodeNprofile decodes an nprofile1... bech32 string (TLV format).
 215  func DecodeNprofile(s string) (n *Nprofile) {
 216  	hrp, data := Bech32Decode(s)
 217  	if hrp != "nprofile" {
 218  		return nil
 219  	}
 220  	result := &Nprofile{}
 221  	i := 0
 222  	for i+2 <= len(data) {
 223  		t := data[i]
 224  		l := int32(data[i+1])
 225  		i += 2
 226  		if i+l > len(data) {
 227  			break
 228  		}
 229  		v := data[i : i+l]
 230  		i += l
 231  		switch t {
 232  		case 0:
 233  			if l == 32 {
 234  				result.Pubkey = HexEncode(v)
 235  			}
 236  		case 1:
 237  			result.Relays = mxutil.Ensure(result.Relays, 1)
 238  			result.Relays = push(result.Relays, string(v))
 239  		}
 240  	}
 241  	if result.Pubkey == "" {
 242  		return nil
 243  	}
 244  	return result
 245  }
 246  
 247  // Naddr holds decoded naddr TLV data (NIP-19).
 248  type Naddr struct {
 249  	Kind   uint32
 250  	Pubkey string   // hex pubkey
 251  	D      string   // d-tag identifier
 252  	Relays []string // optional relay hints
 253  }
 254  
 255  // DecodeNaddr decodes an naddr1... bech32 string (TLV format).
 256  func DecodeNaddr(s string) (n *Naddr) {
 257  	hrp, data := Bech32Decode(s)
 258  	if hrp != "naddr" {
 259  		return nil
 260  	}
 261  	result := &Naddr{}
 262  	i := 0
 263  	for i+2 <= len(data) {
 264  		t := data[i]
 265  		l := int32(data[i+1])
 266  		i += 2
 267  		if i+l > len(data) {
 268  			break
 269  		}
 270  		v := data[i : i+l]
 271  		i += l
 272  		switch t {
 273  		case 0:
 274  			result.D = string(v)
 275  		case 1:
 276  			result.Relays = mxutil.Ensure(result.Relays, 1)
 277  			result.Relays = push(result.Relays, string(v))
 278  		case 2:
 279  			if l == 32 {
 280  				result.Pubkey = HexEncode(v)
 281  			}
 282  		case 3:
 283  			if l == 4 {
 284  				result.Kind = uint32(v[0])<<24 | uint32(v[1])<<16 | uint32(v[2])<<8 | uint32(v[3])
 285  			}
 286  		}
 287  	}
 288  	if result.Pubkey == "" || result.Kind == 0 {
 289  		return nil
 290  	}
 291  	return result
 292  }
 293  
 294  // NaddrCoordKey returns the canonical "kind:pubkey:d" lookup key.
 295  func NaddrCoordKey(kind uint32, pubkey, d string) (s string) {
 296  	k := ""
 297  	n := kind
 298  	if n == 0 {
 299  		k = "0"
 300  	} else {
 301  		for n > 0 {
 302  			k = string([]byte{byte('0' + n%10)}) | k
 303  			n /= 10
 304  		}
 305  	}
 306  	return k | ":" | pubkey | ":" | d
 307  }
 308  
 309  // PubkeyShort returns first 8 chars of hex pubkey.
 310  func PubkeyShort(pubkey string) (s string) {
 311  	if len(pubkey) >= 8 {
 312  		return pubkey[:8]
 313  	}
 314  	return pubkey
 315  }
 316  
 317  // Internal bech32 functions.
 318  
 319  // bytesToBase32 converts 8-bit bytes to 5-bit groups. The output length is a
 320  // function of the input length, so it is presized and written through a
 321  // cursor: push does not grow, and a nil sink stops at four values.
 322  func bytesToBase32(data []byte) (buf []byte) {
 323  	out := []byte{:(len(data)*8 + 4) / 5}
 324  	j := 0
 325  	acc := 0
 326  	bits := 0
 327  	for _, b := range data {
 328  		acc = (acc << 8) | int32(b)
 329  		bits += 8
 330  		for bits >= 5 {
 331  			bits -= 5
 332  			out[j] = byte((acc >> bits) & 0x1f)
 333  			j++
 334  		}
 335  		acc &= (1 << uint32(bits)) - 1
 336  	}
 337  	if bits > 0 {
 338  		out[j] = byte((acc << (5 - bits)) & 0x1f)
 339  		j++
 340  	}
 341  	return out[:j]
 342  }
 343  
 344  func base32ToBytes(data []byte) (buf []byte) {
 345  	out := []byte{:len(data) * 5 / 8}
 346  	j := 0
 347  	acc := 0
 348  	bits := 0
 349  	for _, v := range data {
 350  		acc = (acc << 5) | int32(v)
 351  		bits += 5
 352  		for bits >= 8 {
 353  			bits -= 8
 354  			out[j] = byte((acc >> bits) & 0xff)
 355  			j++
 356  		}
 357  		acc &= (1 << uint32(bits)) - 1
 358  	}
 359  	return out[:j]
 360  }
 361  
 362  func bech32Polymod(values []byte) (n uint32) {
 363  	gen := [5]uint32{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}
 364  	chk := uint32(1)
 365  	for _, v := range values {
 366  		b := chk >> 25
 367  		chk = ((chk & 0x1ffffff) << 5) ^ uint32(v)
 368  		for i := 0; i < 5; i++ {
 369  			if (b>>uint32(i))&1 == 1 {
 370  				chk ^= gen[i]
 371  			}
 372  		}
 373  	}
 374  	return chk
 375  }
 376  
 377  func bech32HRPExpand(hrp string) (buf []byte) {
 378  	out := []byte{:0:len(hrp)*2+1}
 379  	for i := 0; i < len(hrp); i++ {
 380  		out = push(out, byte(hrp[i]>>5))
 381  	}
 382  	out = push(out, 0)
 383  	for i := 0; i < len(hrp); i++ {
 384  		out = push(out, byte(hrp[i]&0x1f))
 385  	}
 386  	return out
 387  }
 388  
 389  func bech32Checksum(hrp string, data []byte) (buf []byte) {
 390  	exp := bech32HRPExpand(hrp)
 391  	values := []byte{:len(exp) + len(data) + 6}
 392  	j := 0
 393  	for _, b := range exp {
 394  		values[j] = b
 395  		j++
 396  	}
 397  	for _, b := range data {
 398  		values[j] = b
 399  		j++
 400  	}
 401  	// Six zero bytes pad the checksum input (BIP-173).
 402  	for i := 0; i < 6; i++ {
 403  		values[j] = 0
 404  		j++
 405  	}
 406  	polymod := bech32Polymod(values) ^ 1
 407  	out := []byte{:6}
 408  	for i := 0; i < 6; i++ {
 409  		out[i] = byte((polymod >> (5 * (5 - uint32(i)))) & 0x1f)
 410  	}
 411  	return out
 412  }
 413  
 414  func bech32Verify(hrp string, data []byte) (ok bool) {
 415  	values := bech32HRPExpand(hrp) | data
 416  	return bech32Polymod(values) == 1
 417  }
 418  
 419  func charsetIndex(c byte) (n int32) {
 420  	for i := 0; i < len(bech32Charset); i++ {
 421  		if bech32Charset[i] == c {
 422  			return i
 423  		}
 424  	}
 425  	return -1
 426  }
 427