clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

feat/ui-updates
tinyrelay / internal / sites / bech32.go
bech32.go 3.0K
1package sites23import (4 "strings"56 "fiatjaf.com/nostr"7)89const bech32Charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"1011func encodeNpub(key nostr.PubKey) string {12 data, _ := convertBits(key[:], 8, 5, true)13 return bech32Encode("npub", data)14}1516func decodeNpub(value string) (nostr.PubKey, bool) {17 prefix, data, ok := bech32Decode(value)18 if !ok || prefix != "npub" {19 return nostr.PubKey{}, false20 }21 raw, ok := convertBits(data, 5, 8, false)22 if !ok || len(raw) != 32 {23 return nostr.PubKey{}, false24 }25 var key nostr.PubKey26 copy(key[:], raw)27 return key, true28}2930func bech32Encode(prefix string, data []byte) string {31 values := make([]byte, len(data))32 for i, value := range data {33 values[i] = value34 }35 checksum := bech32Checksum(prefix, values)36 all := append(values, checksum...)37 var out strings.Builder38 out.WriteString(prefix)39 out.WriteByte('1')40 for _, value := range all {41 out.WriteByte(bech32Charset[value])42 }43 return out.String()44}4546func bech32Decode(value string) (string, []byte, bool) {47 if value != strings.ToLower(value) || len(value) < 8 {48 return "", nil, false49 }50 sep := strings.LastIndexByte(value, '1')51 if sep < 1 || sep+7 > len(value) {52 return "", nil, false53 }54 prefix, encoded := value[:sep], value[sep+1:]55 values := make([]byte, len(encoded))56 for i, char := range encoded {57 pos := strings.IndexByte(bech32Charset, byte(char))58 if pos < 0 {59 return "", nil, false60 }61 values[i] = byte(pos)62 }63 if !bech32Verify(prefix, values) {64 return "", nil, false65 }66 return prefix, values[:len(values)-6], true67}6869func convertBits(data []byte, from, to byte, pad bool) ([]byte, bool) {70 var acc uint71 bits := byte(0)72 max := uint((1 << to) - 1)73 out := make([]byte, 0, len(data)*int(from)/int(to)+1)74 for _, value := range data {75 acc = (acc << from) | uint(value)76 bits += from77 for bits >= to {78 bits -= to79 out = append(out, byte((acc>>bits)&max))80 }81 }82 if pad && bits > 0 {83 out = append(out, byte((acc<<uint(to-bits))&max))84 }85 if !pad && (bits >= from || ((acc<<uint(to-bits))&max) != 0) {86 return nil, false87 }88 return out, true89}9091func bech32Checksum(prefix string, values []byte) []byte {92 all := append(bech32Expand(prefix), values...)93 all = append(all, 0, 0, 0, 0, 0, 0)94 polymod := bech32Polymod(all) ^ 195 out := make([]byte, 6)96 for i := range out {97 out[i] = byte((polymod >> uint(5*(5-i))) & 31)98 }99 return out100}101102func bech32Verify(prefix string, values []byte) bool {103 return bech32Polymod(append(bech32Expand(prefix), values...)) == 1104}105func bech32Expand(prefix string) []byte {106 out := make([]byte, 0, len(prefix)*2+1)107 for _, c := range prefix {108 out = append(out, byte(c)>>5)109 }110 out = append(out, 0)111 for _, c := range prefix {112 out = append(out, byte(c)&31)113 }114 return out115}116func bech32Polymod(values []byte) uint32 {117 generators := [...]uint32{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}118 var chk uint32 = 1119 for _, value := range values {120 top := chk >> 25121 chk = (chk&0x1ffffff)<<5 ^ uint32(value)122 for i, generator := range generators {123 if top>>uint(i)&1 != 0 {124 chk ^= generator125 }126 }127 }128 return chk129}