github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/lnutil/litadr.go (about) 1 package lnutil 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 8 "github.com/mit-dci/lit/bech32" 9 "github.com/mit-dci/lit/btcutil/base58" 10 "github.com/mit-dci/lit/crypto/fastsha256" 11 ) 12 13 // Lit addresses use the bech32 format, but sometimes omit the checksum! 14 // You don't really *need* the checksum with LN identities because you 15 // can't lose money by sending it to the wrong place 16 17 /* types: 18 256 bit full pubkey? (58 char) 19 20 less than 256-bit is the truncated sha256 of the 33 byte pubkey. 21 (probably the easiest way is to drop the first 'q' character 22 23 160-bit with checksum (38 char) 24 LN1eyq3javh983xwlfvamh54r56v9ca6ck70dfrqz 25 26 95-bit without checksum (19 char) 27 ln1eyq3javh983xwlfvamh 28 29 tor uses 80-bit but that seems like it's cutting it close; 95 bit gives 30 a 32K-fold improvement and should be safe for a while. Internal functions 31 use the full pubkey when known, and send the 160-bit checksummed string around, 32 with the last UI layer making the truncation to 19 chars if users want it. 33 34 It's also half the length (after the ln1, which users may not have to type?) of 35 the full address. 36 37 Non-UI code will need to deal with 95-bit truncated hashes for matching, which 38 is a little annoying, but can be stored in 12 bytes (96-bit) with the last bit 39 being wildcard. That should be ok to deal with. 40 41 */ 42 43 func LitFullKeyAdrEncode(in [33]byte) string { 44 withq := bech32.Encode("ln", in[:]) 45 // get rid of the q after the 1. Pubkeys are always 0x02 or 0x03, 46 // so the first 5 bits are always 0. 47 withq = withq[:3] + withq[4:] 48 return withq 49 } 50 51 func LitFullAdrDecode(in string) ([33]byte, error) { 52 var pub [33]byte 53 if len(in) != 61 { 54 return pub, fmt.Errorf("Invalid length, got %d expect 33", len(in)) 55 } 56 // add the q back in so it decodes 57 in = in[:3] + "q" + in[3:] 58 hrp, data, err := bech32.Decode(in) 59 if err != nil { 60 return pub, err 61 } 62 if hrp != "ln" { 63 return pub, fmt.Errorf("Not a ln address, prefix %s", hrp) 64 } 65 copy(pub[:], data) 66 return pub, nil 67 } 68 69 func LitAdrFromPubkey(in [33]byte) string { 70 doubleSha := fastsha256.Sum256(in[:]) 71 return bech32.Encode("ln", doubleSha[:20]) 72 } 73 74 // LitAdrOK make sure the address is OK. Either it has a valid checksum, or 75 // it's shortened and doesn't. 76 func LitAdrOK(adr string) bool { 77 hrp, _, err := bech32.Decode(adr) 78 if hrp != "ln" { 79 return false 80 } 81 if err == nil || len(adr) == 22 { 82 return true 83 } 84 return false 85 } 86 87 // LitAdrBytes takes a lit address string and returns either 20 or 12 bytes. 88 // Or an error. 89 func LitAdrBytes(adr string) ([]byte, error) { 90 if !LitAdrOK(adr) { 91 return nil, fmt.Errorf("invalid ln address %s", adr) 92 } 93 94 _, pkh, err := bech32.Decode(adr) 95 if err == nil { 96 return pkh, nil 97 } 98 // add a q for padding 99 adr = adr + "q" 100 101 truncSquashed, err := bech32.StringToSquashedBytes(adr[3:]) 102 if err != nil { 103 return nil, err 104 } 105 106 truncPKH, err := bech32.Bytes5to8(truncSquashed) 107 if err != nil { 108 return nil, err 109 } 110 return truncPKH, nil 111 } 112 113 // OldAddressFromPKH returns a base58 string from a 20 byte pubkey hash 114 func OldAddressFromPKH(pkHash [20]byte, netID byte) string { 115 return base58.CheckEncode(pkHash[:], netID) 116 } 117 118 // ParseAdrString splits a string like 119 // "ln1yrvw48uc3atg8e2lzs43mh74m39vl785g4ehem@myhost.co:8191 into a separate 120 // pkh part and network part, adding the network part if needed 121 func ParseAdrString(adr string) (string, string) { 122 id, host, port := ParseAdrStringWithPort(adr) 123 if port == 0 { 124 return id, "" 125 } 126 return id, fmt.Sprintf("%s:%d", host, port) 127 } 128 129 func ParseAdrStringWithPort(adr string) (string, string, uint32) { 130 // Check if it's just a number - then it will be returned as the port 131 if !strings.ContainsRune(adr, ':') && !strings.ContainsRune(adr, '@') { 132 port, err := strconv.ParseUint(adr, 10, 32) 133 if err == nil { 134 return "", "127.0.0.1", uint32(port) 135 } 136 } 137 138 // It's not a number, check if it starts with ln1, 139 // if so, return the address and no host info (since it has no @) 140 // Otherwise, assume it's a hostname and return empty adr and default port 141 if !strings.ContainsRune(adr, ':') && !strings.ContainsRune(adr, '@') { 142 if strings.HasPrefix(adr, "ln1") { 143 return adr, "", 0 144 } else { 145 return "", adr, 2448 146 } 147 } 148 149 lnAdr := "" 150 hostSpec := "localhost:2448" 151 152 // If it contains no @ but does contain a semicolon, expect this to be 153 // an empty ln-address but a host with a port 154 if strings.ContainsRune(adr, ':') && !strings.ContainsRune(adr, '@') { 155 hostSpec = adr 156 } 157 158 // If it is formatted like adr@host, but without a semicolon, append 159 // the default port. 160 if !strings.ContainsRune(adr, ':') && strings.ContainsRune(adr, '@') { 161 adr += ":2448" 162 } 163 164 if strings.ContainsRune(adr, '@') { 165 idHost := strings.Split(adr, "@") 166 lnAdr = idHost[0] 167 hostSpec = idHost[1] 168 } 169 170 var host string 171 var port uint32 172 173 hostString := strings.Split(hostSpec, ":") 174 host = hostString[0] 175 if len(hostString) == 1 { 176 // it is :, use default port 177 port = 2448 178 } else { 179 port64, _ := strconv.ParseUint(hostString[1], 10, 32) 180 port = uint32(port64) 181 } 182 if len(host) == 0 { 183 host = "localhost" 184 } 185 return lnAdr, host, port 186 }