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  }