bech32.go 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 := make([]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 := make([]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  // DecodeNpub decodes an npub string to 32 bytes.
  77  func DecodeNpub(s string) []byte {
  78  	hrp, data := Bech32Decode(s)
  79  	if hrp != "npub" || len(data) != 32 {
  80  		return nil
  81  	}
  82  	return data
  83  }
  84  
  85  // DecodeNsec decodes an nsec string to 32 bytes.
  86  func DecodeNsec(s string) []byte {
  87  	hrp, data := Bech32Decode(s)
  88  	if hrp != "nsec" || len(data) != 32 {
  89  		return nil
  90  	}
  91  	return data
  92  }
  93  
  94  // DecodeNote decodes a note string to 32 bytes.
  95  func DecodeNote(s string) []byte {
  96  	hrp, data := Bech32Decode(s)
  97  	if hrp != "note" || len(data) != 32 {
  98  		return nil
  99  	}
 100  	return data
 101  }
 102  
 103  // PubkeyShort returns first 8 chars of hex pubkey.
 104  func PubkeyShort(pubkey string) string {
 105  	if len(pubkey) >= 8 {
 106  		return pubkey[:8]
 107  	}
 108  	return pubkey
 109  }
 110  
 111  // Internal bech32 functions.
 112  
 113  func bytesToBase32(data []byte) []byte {
 114  	var out []byte
 115  	acc := 0
 116  	bits := 0
 117  	for _, b := range data {
 118  		acc = (acc << 8) | int(b)
 119  		bits += 8
 120  		for bits >= 5 {
 121  			bits -= 5
 122  			out = append(out, byte((acc>>bits)&0x1f))
 123  		}
 124  		acc &= (1 << uint(bits)) - 1
 125  	}
 126  	if bits > 0 {
 127  		out = append(out, byte((acc<<(5-bits))&0x1f))
 128  	}
 129  	return out
 130  }
 131  
 132  func base32ToBytes(data []byte) []byte {
 133  	var out []byte
 134  	acc := 0
 135  	bits := 0
 136  	for _, v := range data {
 137  		acc = (acc << 5) | int(v)
 138  		bits += 5
 139  		for bits >= 8 {
 140  			bits -= 8
 141  			out = append(out, byte((acc>>bits)&0xff))
 142  		}
 143  		acc &= (1 << uint(bits)) - 1
 144  	}
 145  	return out
 146  }
 147  
 148  func bech32Polymod(values []byte) uint32 {
 149  	gen := [5]uint32{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}
 150  	chk := uint32(1)
 151  	for _, v := range values {
 152  		b := chk >> 25
 153  		chk = ((chk & 0x1ffffff) << 5) ^ uint32(v)
 154  		for i := 0; i < 5; i++ {
 155  			if (b>>uint(i))&1 == 1 {
 156  				chk ^= gen[i]
 157  			}
 158  		}
 159  	}
 160  	return chk
 161  }
 162  
 163  func bech32HRPExpand(hrp string) []byte {
 164  	out := make([]byte, 0, len(hrp)*2+1)
 165  	for i := 0; i < len(hrp); i++ {
 166  		out = append(out, byte(hrp[i]>>5))
 167  	}
 168  	out = append(out, 0)
 169  	for i := 0; i < len(hrp); i++ {
 170  		out = append(out, byte(hrp[i]&0x1f))
 171  	}
 172  	return out
 173  }
 174  
 175  func bech32Checksum(hrp string, data []byte) []byte {
 176  	values := append(bech32HRPExpand(hrp), data...)
 177  	values = append(values, 0, 0, 0, 0, 0, 0)
 178  	polymod := bech32Polymod(values) ^ 1
 179  	out := make([]byte, 6)
 180  	for i := 0; i < 6; i++ {
 181  		out[i] = byte((polymod >> (5 * (5 - uint(i)))) & 0x1f)
 182  	}
 183  	return out
 184  }
 185  
 186  func bech32Verify(hrp string, data []byte) bool {
 187  	values := append(bech32HRPExpand(hrp), data...)
 188  	return bech32Polymod(values) == 1
 189  }
 190  
 191  func charsetIndex(c byte) int {
 192  	for i := 0; i < len(bech32Charset); i++ {
 193  		if bech32Charset[i] == c {
 194  			return i
 195  		}
 196  	}
 197  	return -1
 198  }
 199