Skip to content
Closed
94 changes: 69 additions & 25 deletions cmd/lit-af/lit-af.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"encoding/hex"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -65,7 +66,7 @@ func newConfigParser(conf *litAfConfig, options flags.Options) *flags.Parser {
return parser
}

func (lc *litAfClient) litAfSetup(conf litAfConfig) {
func (lc *litAfClient) litAfSetup(conf litAfConfig) error {

var err error
// create home directory if it does not exist
Expand All @@ -89,42 +90,81 @@ func (lc *litAfClient) litAfSetup(conf litAfConfig) {
}
logging.SetLogLevel(logLevel) // defaults to zero

// we don't know whether the passed address is a remotePKH or a remotePK
// so we need to detect that here and then take steps accordingly
// so the first part involved here would be either dealing with raw pubkeys or
// dealing with pk hashes
// another question here is how do we deal with connecting to other nodes?
// if we need that in, we need to hcange our overall architecture to host
// pks as well as pk hashes
adr, host, port := lnutil.ParseAdrStringWithPort(conf.Con)
logging.Infof("Adr: %s, Host: %s, Port: %d", adr, host, port)
if litrpc.LndcRpcCanConnectLocallyWithHomeDir(defaultDir) && adr == "" && (host == "localhost" || host == "127.0.0.1") {
// now we've split the address, check if pkh, if not, route straight to noise_xk
if len(adr) == 44 {
// remote PKH, do standard stuff
fmt.Printf("Adr: %s, Host: %s, Port: %d\n", adr, host, port)
if litrpc.LndcRpcCanConnectLocallyWithHomeDir(defaultDir) && adr == "" && (host == "localhost" || host == "127.0.0.1") {

lc.RPCClient, err = litrpc.NewLocalLndcRpcClientWithHomeDirAndPort(defaultDir, port)
if err != nil {
logging.Fatal(err.Error())
}
} else {
if !lnutil.LitAdrOK(adr) {
logging.Fatal("lit address passed in -con parameter is not valid")
}
lc.RPCClient, err = litrpc.NewLocalLndcRpcClientWithHomeDirAndPort(defaultDir, port)
if err != nil {
logging.Fatal(err.Error())
}
} else {
if !lnutil.LitAdrOK(adr) {
logging.Fatal("lit address passed in -con parameter is not valid")
}

keyFilePath := filepath.Join(defaultDir, "lit-af-key.hex")
privKey, err := lnutil.ReadKeyFile(keyFilePath)
if err != nil {
logging.Fatal(err.Error())
}
key, _ := koblitz.PrivKeyFromBytes(koblitz.S256(), privKey[:])
pubkey := key.PubKey().SerializeCompressed() // this is in bytes
fmt.Printf("The pubkey of this lit-af instance is: %s\n", hex.EncodeToString(pubkey))
var temp [33]byte
copy(temp[:], pubkey[:33])
fmt.Printf("The pkh of this lit-af instance is: %s\n", lnutil.LitAdrFromPubkey(temp))
if adr != "" && strings.HasPrefix(adr, "ln1") && host == "" {
ipv4, _, err := lnutil.Lookup(adr, conf.Tracker, "")
if err != nil {
logging.Fatalf("Error looking up address on the tracker: %s", err)
} else {
adr = fmt.Sprintf("%s@%s", adr, ipv4)
}
} else {
adr = fmt.Sprintf("%s@%s:%d", adr, host, port)
}

lc.RPCClient, err = litrpc.NewLndcRpcClient(adr, key)
if err != nil {
logging.Fatal(err.Error())
}
}
} else if len(adr) == 66 {
// remote PK, don't convert to remotePKH
// we have a pubkey, we should't bother checking addresses
// this is definitively a non-lit instance, so don't check for lit stuff here
keyFilePath := filepath.Join(defaultDir, "lit-af-key.hex")
privKey, err := lnutil.ReadKeyFile(keyFilePath)
if err != nil {
logging.Fatal(err.Error())
}
key, _ := koblitz.PrivKeyFromBytes(koblitz.S256(), privKey[:])

if adr != "" && strings.HasPrefix(adr, "ln1") && host == "" {
ipv4, _, err := lnutil.Lookup(adr, conf.Tracker, "")
if err != nil {
logging.Fatalf("Error looking up address on the tracker: %s", err)
} else {
adr = fmt.Sprintf("%s@%s", adr, ipv4)
}
} else {
adr = fmt.Sprintf("%s@%s:%d", adr, host, port)
}

lc.RPCClient, err = litrpc.NewLndcRpcClient(adr, key)
pubkey := key.PubKey().SerializeCompressed() // this is in bytes
fmt.Printf("The pubkey of this lit-af instance is: %s\n", hex.EncodeToString(pubkey))
var temp [33]byte
copy(temp[:], pubkey[:33])
fmt.Printf("The pkh of this lit-af instance is: %s\n", lnutil.LitAdrFromPubkey(temp))
// so this is a non lit node and hence, we don't need to lookup on the tracker (since we already have the pubkey)

// initialize a new rpc instance with noise_xk based authentication here
adr = fmt.Sprintf("%s@%s:%d", adr, host, port)
err = litrpc.NewOtherRpcClient(adr, key)
if err != nil {
logging.Fatal(err.Error())
}
}
return nil
}

// for now just testing how to connect and get messages back and forth
Expand All @@ -137,7 +177,11 @@ func main() {
Dir: defaultDir,
Tracker: defaultTracker,
}
lc.litAfSetup(conf) // setup lit-af to start
err = lc.litAfSetup(conf) // setup lit-af to start
if err != nil {
logging.Error(err)
return
}

rl, err := readline.NewEx(&readline.Config{
Prompt: lnutil.Prompt("lit-af") + lnutil.White("# "),
Expand Down
35 changes: 29 additions & 6 deletions lit.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"runtime"
"syscall"
"time"
"encoding/hex"

"github.com/mit-dci/lit/logging"

Expand Down Expand Up @@ -55,11 +56,12 @@ type litConfig struct { // define a struct for usage with go-flags
Rpcport uint16 `short:"p" long:"rpcport" description:"Set RPC port to connect to"`
Rpchost string `long:"rpchost" description:"Set RPC host to listen to"`
// auto config
AutoReconnect bool `long:"autoReconnect" description:"Attempts to automatically reconnect to known peers periodically."`
AutoReconnectInterval int64 `long:"autoReconnectInterval" description:"The interval (in seconds) the reconnect logic should be executed"`
AutoReconnectOnlyConnectedCoins bool `long:"autoReconnectOnlyConnectedCoins" description:"Only reconnect to peers that we have channels with in a coin whose coin daemon is available"`
AutoListenPort int `long:"autoListenPort" description:"When auto reconnect enabled, starts listening on this port"`
NoAutoListen bool `long:"noautolisten" description:"Don't automatically listen on any ports."`
AutoReconnect bool `long:"autoReconnect" description:"Attempts to automatically reconnect to known peers periodically."`
AutoReconnectInterval int64 `long:"autoReconnectInterval" description:"The interval (in seconds) the reconnect logic should be executed"`
AutoReconnectOnlyConnectedCoins bool `long:"autoReconnectOnlyConnectedCoins" description:"Only reconnect to peers that we have channels with in a coin whose coin daemon is available"`
AutoListenPort int `long:"autoListenPort" description:"When auto reconnect enabled, starts listening on this port"`
NoAutoListen bool `long:"noautolisten" description:"Don't automatically listen on any ports."`
Whitelist string `long:"whitelist" description:"Whitelist a single address so that you can enable a remote peer to login for the first time and authenticate others"`
Params *coinparam.Params
}

Expand Down Expand Up @@ -293,12 +295,33 @@ func main() {
// if we don't link wallet, we can still continue, no worries.
logging.Error(err)
}

logging.Info("Starting lit node")
rpcl := new(litrpc.LitRPC)
rpcl.Node = node
rpcl.OffButton = make(chan bool, 1)
node.RPC = rpcl

Authorization := new(qln.RemoteControlAuthorization)
Authorization.UnansweredRequest = false
Authorization.Allowed = true

// check for whitelisted addresses here
if conf.Whitelist != "" && len(conf.Whitelist) == 66 { // the length of a standard LNAddr
// pass the pubkey here, which is ugly
// we could pass the pkh, have the peer dial us and we could get
// the pubkey during the second round of noise, but maybe overkill?
// we need to decode this hex string into a byte slice
addr, _ := hex.DecodeString(conf.Whitelist)
var temp [33]byte
copy(temp[:33], addr)
logging.Info("Whitelisting address as requested: ",addr)
err = rpcl.Node.SaveRemoteControlAuthorization(temp, Authorization)
if err != nil {
logging.Errorf("Error whitelisting address: %s", err.Error())
// don't fatal since this doesn't affect program flow
}
}

if conf.UnauthRPC {
go litrpc.RPCListen(rpcl, conf.Rpchost, conf.Rpcport)
}
Expand Down
9 changes: 9 additions & 0 deletions litrpc/lndcrpcclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ func NewLocalLndcRpcClientWithHomeDirAndPort(litHomeDir string, port uint32) (*L
return NewLndcRpcClient(adr, key)
}

func NewOtherRpcClient(address string, key *koblitz.PrivateKey)(error) {
// Dial a connection to the lit node
who, where := lnutil.ParseAdrString(address)
_, err := lndc.Dial(key, where, who, net.Dial)
if err != nil {
return err
}
return nil
}
// NewLndcRpcClient creates a new LNDC client using the given private key, which
// is arbitrary. It will then connect to the lit node specified in address, and
// can then exchange remote control calls with it. In order to succesfully
Expand Down
3 changes: 2 additions & 1 deletion litrpc/netcmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ func (r *LitRPC) Connect(args ConnectArgs, reply *ConnectReply) error {
// first, see if the peer to connect to is referenced by peer index.
var connectAdr string
// check if a peer number was supplied instead of a pubkeyhash
// accept either an string or a pubkey (raw)
// so args.LNAddr passed here contains blah@host:ip
peerIdxint, err := strconv.Atoi(args.LNAddr)
// number will mean no error
if err == nil {
Expand All @@ -82,7 +84,6 @@ func (r *LitRPC) Connect(args ConnectArgs, reply *ConnectReply) error {
// use string as is, try to convert to ln address
connectAdr = args.LNAddr
}

err = r.Node.DialPeer(connectAdr)
if err != nil {
return err
Expand Down
1 change: 1 addition & 0 deletions lncore/peers.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type PeerInfo struct {
LnAddr *LnAddr `json:"lnaddr"`
Nickname *string `json:"name"`
NetAddr *string `json:"netaddr"` // ip address, port, I guess
Pubkey *string `json:pubkey`

// TEMP This is again, for adapting to the old system.
PeerIdx uint32 `json:"hint_peeridx"`
Expand Down
63 changes: 48 additions & 15 deletions lndc/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package lndc

import (
"bytes"
"encoding/hex"
"fmt"
"io"
"math"
Expand Down Expand Up @@ -29,28 +30,53 @@ type Conn struct {

// A compile-time assertion to ensure that Conn meets the net.Conn interface.
var _ net.Conn = (*Conn)(nil)
var Noise_XK bool

// Dial attempts to establish an encrypted+authenticated connection with the
// remote peer located at address which has remotePub as its long-term static
// public key. In the case of a handshake failure, the connection is closed and
// a non-nil error is returned.
func Dial(localPriv *koblitz.PrivateKey, ipAddr string, remotePKH string,
func Dial(localPriv *koblitz.PrivateKey, ipAddr string, remoteAddress string,
dialer func(string, string) (net.Conn, error)) (*Conn, error) {
var remotePKH string
var remotePK [33]byte
if remoteAddress[0:3] == "ln1" { // its a remote PKH
remotePKH = remoteAddress
} else if len(remoteAddress) == 66 { // hex encoded remotePK
temp, _ := hex.DecodeString(remoteAddress)
copy(remotePK[:], temp)
logging.Info("Got remote PK: ", remotePK, ", using noise_xk to connect")
SetXKConsts()
}
var conn net.Conn
var err error
conn, err = dialer("tcp", ipAddr)
logging.Info("ipAddr is", ipAddr)
logging.Debug("ipAddr is: ", ipAddr)
if err != nil {
return nil, err
}

b := &Conn{
conn: conn,
noise: NewNoiseMachine(true, localPriv),
b := new(Conn)
if Noise_XK {
// we need to convert the raw PK to a koblitz public key
remotePub, err := koblitz.ParsePubKey(remotePK[:], koblitz.S256())
if err != nil {
logging.Debug(err)
return nil, err
}
b = &Conn{
conn: conn,
noise: NewNoiseXKMachine(true, localPriv, remotePub),
}
} else {
b = &Conn{
conn: conn,
noise: NewNoiseXXMachine(true, localPriv),
}
}

// Initiate the handshake by sending the first act to the receiver.
actOne, err := b.noise.GenActOne()
actOne, err := b.noise.GenActOne(remotePK)
if err != nil {
b.conn.Close()
return nil, err
Expand All @@ -59,7 +85,6 @@ func Dial(localPriv *koblitz.PrivateKey, ipAddr string, remotePKH string,
b.conn.Close()
return nil, err
}

// We'll ensure that we get ActTwo from the remote peer in a timely
// manner. If they don't respond within 1s, then we'll kill the
// connection.
Expand All @@ -69,22 +94,30 @@ func Dial(localPriv *koblitz.PrivateKey, ipAddr string, remotePKH string,
// remotePub), then read the second act after which we'll be able to
// send our static public key to the remote peer with strong forward
// secrecy.
var actTwo [ActTwoSize]byte
actTwo := make([]byte, ActTwoSize)
if _, err := io.ReadFull(conn, actTwo[:]); err != nil {
b.conn.Close()
return nil, err
}
s, err := b.noise.RecvActTwo(actTwo)
if err != nil {
b.conn.Close()
return nil, err
if !Noise_XK {
remotePK, err = b.noise.RecvActTwo(actTwo)
if err != nil {
b.conn.Close()
return nil, err
}
} else {
if _, err := b.noise.RecvActTwo(actTwo); err != nil {
b.conn.Close()
return nil, err
}
}

logging.Info("Received pubkey", s)
if lnutil.LitAdrFromPubkey(s) != remotePKH {
logging.Infoln("Received pubkey: ", remotePK)
if lnutil.LitAdrFromPubkey(remotePK) != remotePKH && !Noise_XK {
// for noise_XK dont check PKH and PK because we'd have already checked this
// the last time we connected to this guy
return nil, fmt.Errorf("Remote PKH doesn't match. Quitting!")
}
logging.Infof("Received PKH %s matches", lnutil.LitAdrFromPubkey(s))

// Finally, complete the handshake by sending over our encrypted static
// key and execute the final ECDH operation.
Expand Down
Loading