diff --git a/lndc/conn.go b/lndc/conn.go deleted file mode 100644 index cd12865a..00000000 --- a/lndc/conn.go +++ /dev/null @@ -1,363 +0,0 @@ -package lndc - -import ( - "bytes" - "crypto/cipher" - "crypto/hmac" - "encoding/binary" - "fmt" - "net" - "time" - - "github.com/btcsuite/fastsha256" - "github.com/codahale/chacha20poly1305" - "github.com/roasbeef/btcutil" - - "github.com/roasbeef/btcd/btcec" -) - -// Conn... -type LNDConn struct { - RemotePub *btcec.PublicKey - RemoteLNId [16]byte - - myNonceInt uint64 - remoteNonceInt uint64 - - // If Authed == false, the remotePub is the EPHEMERAL key. - // once authed == true, remotePub is who you're actually talking to. - Authed bool - - // chachaStream saves some time as you don't have to init it with - // the session key every time. Make SessionKey redundant; remove later. - chachaStream cipher.AEAD - - // ViaPbx specifies whether this is a direct TCP connection or an - // encapsulated PBX connection. - // If going ViaPbx, Cn isn't used channels are used for Read() and - // Write(), which are filled by the PBXhandler. - ViaPbx bool - PbxIncoming chan []byte - PbxOutgoing chan []byte - - version uint8 - - readBuf bytes.Buffer - - Conn net.Conn -} - -// NewConn... -func NewConn(conn net.Conn) *LNDConn { - return &LNDConn{Conn: conn} -} - -// Dial... -func (c *LNDConn) Dial( - myId *btcec.PrivateKey, address string, remoteId []byte) error { - var err error - - if !c.ViaPbx { - if c.Conn != nil { - return fmt.Errorf("connection already established") - } - - // First, open the TCP connection itself. - c.Conn, err = net.Dial("tcp", address) - if err != nil { - return err - } - } - - // Before dialing out to the remote host, verify that `remoteId` is either - // a pubkey or a pubkey hash. - if len(remoteId) != 33 && len(remoteId) != 20 { - return fmt.Errorf("must supply either remote pubkey or " + - "pubkey hash") - } - - // Calc remote LNId; need this for creating pbx connections just because - // LNid is in the struct does not mean it's authed! - if len(remoteId) == 20 { - copy(c.RemoteLNId[:], remoteId[:16]) - } else { - theirAdr := btcutil.Hash160(remoteId) - copy(c.RemoteLNId[:], theirAdr[:16]) - } - - // Make up an ephemeral keypair for this session. - ourEphemeralPriv, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - return err - } - ourEphemeralPub := ourEphemeralPriv.PubKey() - - // Send 1. Send my ephemeral pubkey. Can add version bits. - if _, err = writeClear(c.Conn, ourEphemeralPub.SerializeCompressed()); err != nil { - return err - } - - // Read, then deserialize their ephemeral public key. - theirEphPubBytes, err := readClear(c.Conn) - if err != nil { - return err - } - theirEphPub, err := btcec.ParsePubKey(theirEphPubBytes, btcec.S256()) - if err != nil { - return err - } - - // Do non-interactive diffie with ephemeral pubkeys. Sha256 for good - // luck. - sessionKey := fastsha256.Sum256( - btcec.GenerateSharedSecret(ourEphemeralPriv, theirEphPub), - ) - - // Now that we've derive the session key, we can initialize the - // chacha20poly1305 AEAD instance which will be used for the remainder of - // the session. - c.chachaStream, err = chacha20poly1305.New(sessionKey[:]) - if err != nil { - return err - } - - c.myNonceInt = 1 << 63 - c.remoteNonceInt = 0 - - c.RemotePub = theirEphPub - c.Authed = false - - // Session is now open and confidential but not yet authenticated... - // So auth! - if len(remoteId) == 20 { - // Only know pubkey hash (20 bytes). - err = c.authPKH(myId, remoteId, ourEphemeralPub.SerializeCompressed()) - } else { - // Must be 33 byte pubkey. - err = c.authPubKey(myId, remoteId, ourEphemeralPub.SerializeCompressed()) - } - if err != nil { - return err - } - - return nil -} - -// authPubKey... -func (c *LNDConn) authPubKey( - myId *btcec.PrivateKey, remotePubBytes, localEphPubBytes []byte) error { - if c.Authed { - return fmt.Errorf("%s already authed", c.RemotePub) - } - - // Since we already know their public key, we can immediately generate - // the DH proof without an additional round-trip. - theirPub, err := btcec.ParsePubKey(remotePubBytes, btcec.S256()) - if err != nil { - return err - } - theirPKH := btcutil.Hash160(remotePubBytes) - idDH := fastsha256.Sum256(btcec.GenerateSharedSecret(myId, theirPub)) - myDHproof := btcutil.Hash160(append(c.RemotePub.SerializeCompressed(), idDH[:]...)) - - // Send over the 73 byte authentication message: my pubkey, their - // pubkey hash, DH proof. - var authMsg [73]byte - copy(authMsg[:33], myId.PubKey().SerializeCompressed()) - copy(authMsg[33:], theirPKH) - copy(authMsg[53:], myDHproof) - if _, err = c.Conn.Write(authMsg[:]); err != nil { - return nil - } - - // Await, their response. They should send only the 20-byte DH proof. - resp := make([]byte, 20) - _, err = c.Conn.Read(resp) - if err != nil { - return err - } - - // Verify that their proof matches our locally computed version. - theirDHproof := btcutil.Hash160(append(localEphPubBytes, idDH[:]...)) - if !hmac.Equal(resp, theirDHproof) { - return fmt.Errorf("invalid DH proof %x", theirDHproof) - } - - // Proof checks out, auth complete. - c.RemotePub = theirPub - theirAdr := btcutil.Hash160(theirPub.SerializeCompressed()) - copy(c.RemoteLNId[:], theirAdr[:16]) - c.Authed = true - - return nil -} - -// authPKH... -func (c *LNDConn) authPKH( - myId *btcec.PrivateKey, theirPKH, localEphPubBytes []byte) error { - if c.Authed { - return fmt.Errorf("%s already authed", c.RemotePub) - } - if len(theirPKH) != 20 { - return fmt.Errorf("remote PKH must be 20 bytes, got %d", - len(theirPKH)) - } - - // Send 53 bytes: our pubkey, and the remote's pubkey hash. - var greetingMsg [53]byte - copy(greetingMsg[:33], myId.PubKey().SerializeCompressed()) - copy(greetingMsg[33:], theirPKH) - if _, err := c.Conn.Write(greetingMsg[:]); err != nil { - return err - } - - // Wait for their response. - // TODO(tadge): add timeout here - // * NOTE(roasbeef): read timeout should be set on the underlying - // net.Conn. - resp := make([]byte, 53) - if _, err := c.Conn.Read(resp); err != nil { - return err - } - - // Parse their long-term public key, and generate the DH proof. - theirPub, err := btcec.ParsePubKey(resp[:33], btcec.S256()) - if err != nil { - return err - } - idDH := fastsha256.Sum256(btcec.GenerateSharedSecret(myId, theirPub)) - theirDHproof := btcutil.Hash160(append(localEphPubBytes, idDH[:]...)) - - // Verify that their DH proof matches the one we just generated. - if !hmac.Equal(resp[33:], theirDHproof) { - return fmt.Errorf("Invalid DH proof %x", theirDHproof) - } - - // If their DH proof checks out, then send our own. - myDHproof := btcutil.Hash160(append(c.RemotePub.SerializeCompressed(), idDH[:]...)) - if _, err = c.Conn.Write(myDHproof); err != nil { - return err - } - - // Proof sent, auth complete. - c.RemotePub = theirPub - theirAdr := btcutil.Hash160(theirPub.SerializeCompressed()) - copy(c.RemoteLNId[:], theirAdr[:16]) - c.Authed = true - - return nil -} - -// Read reads data from the connection. -// Read can be made to time out and return a Error with Timeout() == true -// after a fixed time limit; see SetDeadline and SetReadDeadline. -// Part of the net.Conn interface. -func (c *LNDConn) Read(b []byte) (n int, err error) { - // In order to reconcile the differences between the record abstraction - // of our AEAD connection, and the stream abstraction of TCP, we maintain - // an intermediate read buffer. If this buffer becomes depleated, then - // we read the next record, and feed it into the buffer. Otherwise, we - // read directly from the buffer. - if c.readBuf.Len() == 0 { - // The buffer is empty, so read the next cipher text. - ctext, err := readClear(c.Conn) - if err != nil { - return 0, err - } - - // Encode the current remote nonce, so we can use it to decrypt - // the cipher text. - var nonceBuf [8]byte - binary.BigEndian.PutUint64(nonceBuf[:], c.remoteNonceInt) - - c.remoteNonceInt++ // increment remote nonce, no matter what... - - msg, err := c.chachaStream.Open(nil, nonceBuf[:], ctext, nil) - if err != nil { - return 0, err - } - - if _, err := c.readBuf.Write(msg); err != nil { - return 0, err - } - } - - return c.readBuf.Read(b) -} - -// Write writes data to the connection. -// Write can be made to time out and return a Error with Timeout() == true -// after a fixed time limit; see SetDeadline and SetWriteDeadline. -// Part of the net.Conn interface. -func (c *LNDConn) Write(b []byte) (n int, err error) { - if b == nil { - return 0, fmt.Errorf("write to %x nil", c.RemoteLNId) - } - - // first encrypt message with shared key - var nonceBuf [8]byte - binary.BigEndian.PutUint64(nonceBuf[:], c.myNonceInt) - c.myNonceInt++ // increment mine - - ctext := c.chachaStream.Seal(nil, nonceBuf[:], b, nil) - if err != nil { - return 0, err - } - if len(ctext) > 65530 { - return 0, fmt.Errorf("Write to %x too long, %d bytes", - c.RemoteLNId, len(ctext)) - } - - // use writeClear to prepend length / destination header - return writeClear(c.Conn, ctext) -} - -// Close closes the connection. -// Any blocked Read or Write operations will be unblocked and return errors. -// Part of the net.Conn interface. -func (c *LNDConn) Close() error { - c.myNonceInt = 0 - c.remoteNonceInt = 0 - c.RemotePub = nil - - return c.Conn.Close() -} - -// LocalAddr returns the local network address. -// Part of the net.Conn interface. -// If PBX reports address of pbx host. -func (c *LNDConn) LocalAddr() net.Addr { - return c.Conn.LocalAddr() -} - -// RemoteAddr returns the remote network address. -// Part of the net.Conn interface. -func (c *LNDConn) RemoteAddr() net.Addr { - return c.Conn.RemoteAddr() -} - -// SetDeadline sets the read and write deadlines associated -// with the connection. It is equivalent to calling both -// SetReadDeadline and SetWriteDeadline. -// Part of the net.Conn interface. -func (c *LNDConn) SetDeadline(t time.Time) error { - return c.Conn.SetDeadline(t) -} - -// SetReadDeadline sets the deadline for future Read calls. -// A zero value for t means Read will not time out. -// Part of the net.Conn interface. -func (c *LNDConn) SetReadDeadline(t time.Time) error { - return c.Conn.SetReadDeadline(t) -} - -// SetWriteDeadline sets the deadline for future Write calls. -// Even if write times out, it may return n > 0, indicating that -// some of the data was successfully written. -// A zero value for t means Write will not time out. -// Part of the net.Conn interface. -func (c *LNDConn) SetWriteDeadline(t time.Time) error { - return c.Conn.SetWriteDeadline(t) -} - -var _ net.Conn = (*LNDConn)(nil) diff --git a/lndc/listener.go b/lndc/listener.go deleted file mode 100644 index 40f67fb1..00000000 --- a/lndc/listener.go +++ /dev/null @@ -1,202 +0,0 @@ -package lndc - -import ( - "bytes" - "fmt" - "net" - - "github.com/btcsuite/fastsha256" - "github.com/codahale/chacha20poly1305" - "github.com/roasbeef/btcd/btcec" - "github.com/roasbeef/btcutil" -) - -// Listener... -type Listener struct { - longTermPriv *btcec.PrivateKey - - tcp *net.TCPListener -} - -var _ net.Listener = (*Listener)(nil) - -// NewListener... -func NewListener(localPriv *btcec.PrivateKey, listenAddr string) (*Listener, error) { - addr, err := net.ResolveTCPAddr("tcp", listenAddr) - if err != nil { - return nil, err - } - - l, err := net.ListenTCP("tcp", addr) - if err != nil { - return nil, err - } - - return &Listener{localPriv, l}, nil -} - -// Accept waits for and returns the next connection to the listener. -// Part of the net.Listener interface. -func (l *Listener) Accept() (c net.Conn, err error) { - conn, err := l.tcp.Accept() - if err != nil { - return nil, err - } - - lndc := NewConn(conn) - - // Exchange an ephemeral public key with the remote connection in order - // to establish a confidential connection before we attempt to - // authenticated. - ephemeralKey, err := l.createCipherConn(lndc) - if err != nil { - return nil, err - } - - // Now that we've established an encrypted connection, authenticate the - // identity of the remote host. - ephemeralPub := ephemeralKey.PubKey().SerializeCompressed() - if err := l.authenticateConnection(l.longTermPriv, lndc, ephemeralPub); err != nil { - lndc.Close() - return nil, err - } - - return lndc, nil -} - -// createCipherConn.... -func (l *Listener) createCipherConn(lnConn *LNDConn) (*btcec.PrivateKey, error) { - var err error - var theirEphPubBytes []byte - - // First, read and deserialize their ephemeral public key. - theirEphPubBytes, err = readClear(lnConn.Conn) - if err != nil { - return nil, err - } - if len(theirEphPubBytes) != 33 { - return nil, fmt.Errorf("Got invalid %d byte eph pubkey %x\n", - len(theirEphPubBytes), theirEphPubBytes) - } - theirEphPub, err := btcec.ParsePubKey(theirEphPubBytes, btcec.S256()) - if err != nil { - return nil, err - } - - // Once we've parsed and verified their key, generate, and send own - // ephemeral key pair for use within this session. - myEph, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - return nil, err - } - if _, err := writeClear(lnConn.Conn, myEph.PubKey().SerializeCompressed()); err != nil { - return nil, err - } - - // Now that we have both keys, do non-interactive diffie with ephemeral - // pubkeys, sha256 for good luck. - sessionKey := fastsha256.Sum256( - btcec.GenerateSharedSecret(myEph, theirEphPub), - ) - - lnConn.chachaStream, err = chacha20poly1305.New(sessionKey[:]) - - lnConn.remoteNonceInt = 1 << 63 - lnConn.myNonceInt = 0 - - lnConn.RemotePub = theirEphPub - lnConn.Authed = false - - return myEph, nil -} - -// AuthListen... -func (l *Listener) authenticateConnection( - myId *btcec.PrivateKey, lnConn *LNDConn, localEphPubBytes []byte) error { - var err error - - // TODO(roasbeef): should be using read/write clear here? - slice := make([]byte, 73) - n, err := lnConn.Conn.Read(slice) - if err != nil { - return err - } - - authmsg := slice[:n] - if len(authmsg) != 53 && len(authmsg) != 73 { - return fmt.Errorf("got auth message of %d bytes, "+ - "expect 53 or 73", len(authmsg)) - } - - myPKH := btcutil.Hash160(l.longTermPriv.PubKey().SerializeCompressed()) - if !bytes.Equal(myPKH, authmsg[33:53]) { - return fmt.Errorf( - "remote host asking for PKH %x, that's not me", authmsg[33:53]) - } - - // do DH with id keys - theirPub, err := btcec.ParsePubKey(authmsg[:33], btcec.S256()) - if err != nil { - return err - } - idDH := fastsha256.Sum256( - btcec.GenerateSharedSecret(l.longTermPriv, theirPub), - ) - - myDHproof := btcutil.Hash160( - append(lnConn.RemotePub.SerializeCompressed(), idDH[:]...), - ) - theirDHproof := btcutil.Hash160(append(localEphPubBytes, idDH[:]...)) - - // If they already know our public key, then execute the fast path. - // Verify their DH proof, and send our own. - if len(authmsg) == 73 { - // Verify their DH proof. - if !bytes.Equal(authmsg[53:], theirDHproof) { - return fmt.Errorf("invalid DH proof from %s", - lnConn.RemoteAddr().String()) - } - - // Their DH proof checks out, so send ours now. - if _, err = lnConn.Conn.Write(myDHproof); err != nil { - return err - } - } else { - // Otherwise, they don't yet know our public key. So we'll send - // it over to them, so we can both compute the DH proof. - msg := append(l.longTermPriv.PubKey().SerializeCompressed(), myDHproof...) - if _, err = lnConn.Conn.Write(msg); err != nil { - return err - } - - resp := make([]byte, 20) - if _, err := lnConn.Conn.Read(resp); err != nil { - return err - } - - // Verify their DH proof. - if bytes.Equal(resp, theirDHproof) == false { - return fmt.Errorf("Invalid DH proof %x", theirDHproof) - } - } - - theirAdr := btcutil.Hash160(theirPub.SerializeCompressed()) - copy(lnConn.RemoteLNId[:], theirAdr[:16]) - lnConn.RemotePub = theirPub - lnConn.Authed = true - - return nil -} - -// Close closes the listener. -// Any blocked Accept operations will be unblocked and return errors. -// Part of the net.Listener interface. -func (l *Listener) Close() error { - return l.tcp.Close() -} - -// Addr returns the listener's network address. -// Part of the net.Listener interface. -func (l *Listener) Addr() net.Addr { - return l.tcp.Addr() -} diff --git a/lndc/lnadr.go b/lndc/lnadr.go deleted file mode 100644 index 8daa9f11..00000000 --- a/lndc/lnadr.go +++ /dev/null @@ -1,191 +0,0 @@ -package lndc - -import ( - "bytes" - "encoding/binary" - "encoding/hex" - "fmt" - "net" - "strings" - - "github.com/roasbeef/btcd/btcec" - "github.com/roasbeef/btcd/chaincfg" - "github.com/roasbeef/btcutil" -) - -// lnAddr... -// TODO(roasbeef): revamp -type LNAdr struct { - LnID [16]byte // redundant because adr contains it - PubKey *btcec.PublicKey - - Base58Adr btcutil.Address // Base58 encoded address (1XXX...) - NetAddr *net.TCPAddr // IP address - - name string // human readable name? Not a thing yet. - host string // internet host this ID is reachable at. also not a thing - endorsement []byte // a sig confirming the name? Not implemented - - net *chaincfg.Params -} - -// newLnAdr.... -func NewLnAdr(addr *net.TCPAddr, pubkey *btcec.PublicKey, - net *chaincfg.Params) (*LNAdr, error) { - - hash160 := btcutil.Hash160(pubkey.SerializeCompressed()) - pkh, err := btcutil.NewAddressPubKeyHash(hash160, net) - if err != nil { - return nil, err - } - - return &LNAdr{ - PubKey: pubkey, - Base58Adr: pkh, - NetAddr: addr, - net: net, - }, nil -} - -// newLnAddr... -func LnAddrFromString(encodedAddr string, netParams *chaincfg.Params) (*LNAdr, error) { - // The format of an lnaddr is "@host" - idHost := strings.Split(encodedAddr, "@") - if len(idHost) != 2 { - return nil, fmt.Errorf("invalid format for lnaddr string: %v", encodedAddr) - } - - // Attempt to resolve the IP address, this handles parsing IPv6 zones, - // and such. - ipAddr, err := net.ResolveTCPAddr("tcp", idHost[1]) - if err != nil { - return nil, err - } - - addr := &LNAdr{NetAddr: ipAddr, net: netParams} - - idLen := len(idHost[0]) - switch { - // Is the ID a hex-encoded compressed public key? - case idLen > 65 && idLen < 69: - pubkeyBytes, err := hex.DecodeString(idHost[0]) - if err != nil { - return nil, err - } - - addr.PubKey, err = btcec.ParsePubKey(pubkeyBytes, btcec.S256()) - if err != nil { - return nil, err - } - - // got pubey, populate address from pubkey - pkh := btcutil.Hash160(addr.PubKey.SerializeCompressed()) - addr.Base58Adr, err = btcutil.NewAddressPubKeyHash(pkh, - netParams) - if err != nil { - return nil, err - } - // Is the ID a string encoded bitcoin address? - case idLen > 33 && idLen < 37: - addr.Base58Adr, err = btcutil.DecodeAddress(idHost[0], - netParams) - if err != nil { - return nil, err - } - default: - return nil, fmt.Errorf("invalid address %s", idHost[0]) - } - - // Finally, populate the lnid from the address. - copy(addr.LnID[:], addr.Base58Adr.ScriptAddress()) - - return addr, nil -} - -// Deserialize an LNId from byte slice (on disk) -// Note that this does not check any internal consistency, because on local -// storage there's no point. Check separately if needed. -// Also, old and probably needs to be changed / updated -func (l *LNAdr) Deserialize(s []byte) error { - b := bytes.NewBuffer(s) - - // Fail if on-disk LNId too short - if b.Len() < 24 { // 24 is min lenght - return fmt.Errorf("can't read LNId - too short") - } - // read indicator of pubkey or pubkeyhash - x, err := b.ReadByte() - if err != nil { - return err - } - if x == 0xb0 { // for pubkey storage - // read 33 bytes of pubkey - l.PubKey, err = btcec.ParsePubKey(b.Next(33), btcec.S256()) - if err != nil { - return err - } - - l.Base58Adr, err = btcutil.NewAddressPubKeyHash( - btcutil.Hash160(l.PubKey.SerializeCompressed()), - &chaincfg.TestNet3Params) - if err != nil { - return err - } - } else if x == 0xa0 { // for pubkeyhash storage - l.Base58Adr, err = btcutil.NewAddressPubKeyHash( - b.Next(20), &chaincfg.TestNet3Params) - if err != nil { - return err - } - } else { - return fmt.Errorf("Unknown lnid indicator byte %x", x) - } - - var nameLen, hostLen, endorseLen uint8 - - // read name length - err = binary.Read(b, binary.BigEndian, &nameLen) - if err != nil { - return err - } - // if name non-zero, read name - if nameLen > 0 { - l.name = string(b.Next(int(nameLen))) - } - - // read host length - err = binary.Read(b, binary.BigEndian, &hostLen) - if err != nil { - return err - } - // if host non-zero, read host - if hostLen > 0 { - l.host = string(b.Next(int(hostLen))) - } - - // read endorsement length - err = binary.Read(b, binary.BigEndian, &endorseLen) - if err != nil { - return err - } - // if endorsement non-zero, read endorsement - if endorseLen > 0 { - l.endorsement = b.Next(int(endorseLen)) - } - - return nil -} - -// String... -func (l *LNAdr) String() string { - var encodedId []byte - if l.Base58Adr != nil { - encodedId = l.Base58Adr.ScriptAddress() - } else { - pubKey := l.PubKey.SerializeCompressed() - pkh, _ := btcutil.NewAddressPubKeyHash(pubKey, l.net) - encodedId = pkh.ScriptAddress() - } - - return fmt.Sprintf("%v@%v", hex.EncodeToString(encodedId), l.NetAddr) -} diff --git a/lndc/lndc_test.go b/lndc/lndc_test.go deleted file mode 100644 index e5421ea1..00000000 --- a/lndc/lndc_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package lndc - -import ( - "bytes" - "fmt" - "sync" - "testing" - - "github.com/roasbeef/btcd/btcec" -) - -func TestConnectionCorrectness(t *testing.T) { - // First, generate the long-term private keys both ends of the connection - // within our test. - localPriv, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Fatalf("unable to generate local priv key: %v", err) - } - remotePriv, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Fatalf("unable to generate remote priv key: %v", err) - } - - // Having a port of "0" means a random port will be chosen for our - // listener. - addr := "127.0.0.1:0" - - // Our listener will be local, and the connection remote. - listener, err := NewListener(localPriv, addr) - if err != nil { - t.Fatalf("unable to create listener: %v", err) - } - conn := NewConn(nil) - - var wg sync.WaitGroup - var dialErr error - - // Initiate a connection with a separate goroutine, and listen with our - // main one. If both errors are nil, then encryption+auth was succesful. - wg.Add(1) - go func() { - dialErr = conn.Dial(remotePriv, listener.Addr().String(), - localPriv.PubKey().SerializeCompressed()) - wg.Done() - }() - - localConn, listenErr := listener.Accept() - if listenErr != nil { - t.Fatalf("unable to accept connection: %v", listenErr) - } - - wg.Wait() - - if dialErr != nil { - t.Fatalf("unable to establish connection: %v", dialErr) - } - - // Test out some message full-message reads. - for i := 0; i < 10; i++ { - msg := []byte("hello" + string(i)) - - if _, err := conn.Write(msg); err != nil { - t.Fatalf("remote conn failed to write: %v", err) - } - - readBuf := make([]byte, len(msg)) - if _, err := localConn.Read(readBuf); err != nil { - t.Fatalf("local conn failed to read: %v", err) - } - - if !bytes.Equal(readBuf, msg) { - t.Fatalf("messages don't match, %v vs %v", - string(readBuf), string(msg)) - } - } - - // Now try incremental message reads. This simulates first writing a - // message header, then a message body. - outMsg := []byte("hello world") - fmt.Println("write") - if _, err := conn.Write(outMsg); err != nil { - t.Fatalf("remote conn failed to write: %v", err) - } - - readBuf := make([]byte, len(outMsg)) - if _, err := localConn.Read(readBuf[:len(outMsg)/2]); err != nil { - t.Fatalf("local conn failed to read: %v", err) - } - if _, err := localConn.Read(readBuf[len(outMsg)/2:]); err != nil { - t.Fatalf("local conn failed to read: %v", err) - } - - if !bytes.Equal(outMsg, readBuf) { - t.Fatalf("messages don't match, %v vs %v", - string(readBuf), string(outMsg)) - } -} diff --git a/lndc/netio.go b/lndc/netio.go deleted file mode 100644 index c7525d15..00000000 --- a/lndc/netio.go +++ /dev/null @@ -1,91 +0,0 @@ -package lndc - -import ( - "bytes" - "encoding/binary" - "fmt" - "io" - "net" -) - -// New & improved tcp open session. -// There's connector A and listener B. Once the connection is set up there's no -// difference, but there can be during the setup. -// Setup: -// 1 -> A sends B ephemeral secp256k1 pubkey (33 bytes) -// 2 <- B sends A ephemeral secp256k1 pubkey (33 bytes) -// A and B do DH, get a shared secret. -// ========== -// Seesion is open! Done! Well not quite. Session is confidential but not -// yet authenticated. From here on, can use the Send() and Recv() functions with -// chacha20poly1305. -// ========== - -// Nodes authenticate by doing a DH with their persistent identity keys, and then -// exchanging hash based proofs that they got the same shared IDDH secret. -// The DH proof is h160(remote eph pubkey, IDDH secret) -// A initiates auth. -// -// If A does not know B's pubkey but only B's pubkey hash: -// -// 1 -> A sends [PubKeyA, PubKeyHashB] (53 bytes) -// B computes ID pubkey DH -// 2 <- B sends [PubkeyB, DH proof] (53 bytes) -// 3 -> A sends DH proof (20 bytes) -// done. -// -// This exchange can be sped up if A already knows B's pubkey: -// -// A already knows who they're talking to, or trying to talk to -// 1 -> A sends [PubKeyA, PubkeyHashB, DH proof] (73 bytes) -// 2 <- B sends DH proof (20 bytes) -// -// A and B both verify those H160 hashes, and if matching consider their -// session counterparty authenticated. -// -// A possible weakness of the DH proof is if B re-uses eph keys. That potentially -// makes *A*'s proof weaker though. A gets to choose the proof B creates. As -// long as your software makes new eph keys each time, you should be OK. - -// readClear and writeClear don't encrypt but directly read and write to the -// underlying data link, only adding or subtracting a 2 byte length header. -// All Read() and Write() calls for lndc's use these functions internally -// (they aren't exported). They're also used in the key agreement phase. - -// readClear reads the next length-prefixed message from the underlying raw -// TCP connection. -func readClear(c net.Conn) ([]byte, error) { - var msgLen uint16 - - if err := binary.Read(c, binary.BigEndian, &msgLen); err != nil { - return nil, err - } - - msg := make([]byte, msgLen) - if _, err := io.ReadFull(c, msg); err != nil { - return nil, err - } - - return msg, nil -} - -// TODO(roasbeef): incorporate buffer pool - -// writeClear writes the passed message with a prefixed 2-byte length header. -func writeClear(conn net.Conn, msg []byte) (int, error) { - if len(msg) > 65530 { - return 0, fmt.Errorf("lmsg too long, %d bytes", len(msg)) - } - - // Add 2 byte length header (pbx doesn't need it) and send over TCP. - var msgBuf bytes.Buffer - if err := binary.Write(&msgBuf, binary.BigEndian, uint16(len(msg))); err != nil { - return 0, err - } - - if _, err := msgBuf.Write(msg); err != nil { - return 0, err - } - - return conn.Write(msgBuf.Bytes()) -} diff --git a/shell.go b/shell.go deleted file mode 100644 index ebf8cea2..00000000 --- a/shell.go +++ /dev/null @@ -1,427 +0,0 @@ -package main - -import ( - "bufio" - "fmt" - "log" - "os" - "sort" - "strconv" - "strings" - - "github.com/roasbeef/btcd/chaincfg" - "github.com/roasbeef/btcutil" - - "github.com/lightningnetwork/lnd/uspv" -) - -/* this is a CLI shell for testing out LND. Right now it's only for uspv -testing. It can send and receive coins. -*/ - -const ( - keyFileName = "testkey.hex" - headerFileName = "headers.bin" - dbFileName = "utxo.db" - // this is my local testnet node, replace it with your own close by. - // Random internet testnet nodes usually work but sometimes don't, so - // maybe I should test against different versions out there. - SPVHostAdr = "127.0.0.1:28333" -) - -var ( - Params = &chaincfg.SegNet4Params - SCon uspv.SPVCon // global here for now -) - -func shell(SPVHostAdr string, Params *chaincfg.Params) { - fmt.Printf("LND spv shell v0.0\n") - fmt.Printf("Not yet well integrated, but soon.\n") - - // read key file (generate if not found) - rootPriv, err := uspv.ReadKeyFileToECPriv(keyFileName, Params) - if err != nil { - log.Fatal(err) - } - // setup TxStore first (before spvcon) - Store := uspv.NewTxStore(rootPriv, Params) - // setup spvCon - - SCon, err = uspv.OpenSPV( - SPVHostAdr, headerFileName, dbFileName, &Store, true, false, Params) - if err != nil { - log.Fatal(err) - } - - tip, err := SCon.TS.GetDBSyncHeight() // ask for sync height - if err != nil { - log.Fatal(err) - } - if tip == 0 { // DB has never been used, set to birthday - tip = 21900 // hardcoded; later base on keyfile date? - err = SCon.TS.SetDBSyncHeight(tip) - if err != nil { - log.Fatal(err) - } - } - - // once we're connected, initiate headers sync - err = SCon.AskForHeaders() - if err != nil { - log.Fatal(err) - } - - // main shell loop - for { - // setup reader with max 4K input chars - reader := bufio.NewReaderSize(os.Stdin, 4000) - fmt.Printf("LND# ") // prompt - msg, err := reader.ReadString('\n') // input finishes on enter key - if err != nil { - log.Fatal(err) - } - - cmdslice := strings.Fields(msg) // chop input up on whitespace - if len(cmdslice) < 1 { - continue // no input, just prompt again - } - fmt.Printf("entered command: %s\n", msg) // immediate feedback - err = Shellparse(cmdslice) - if err != nil { // only error should be user exit - log.Fatal(err) - } - } - return -} - -// Shellparse parses user input and hands it to command functions if matching -func Shellparse(cmdslice []string) error { - var err error - var args []string - cmd := cmdslice[0] - if len(cmdslice) > 1 { - args = cmdslice[1:] - } - if cmd == "exit" || cmd == "quit" { - return fmt.Errorf("User exit") - } - - // help gives you really terse help. Just a list of commands. - if cmd == "help" { - err = Help(args) - if err != nil { - fmt.Printf("help error: %s\n", err) - } - return nil - } - - // adr generates a new address and displays it - if cmd == "adr" { - err = Adr(args) - if err != nil { - fmt.Printf("adr error: %s\n", err) - } - return nil - } - - // bal shows the current set of utxos, addresses and score - if cmd == "bal" { - err = Bal(args) - if err != nil { - fmt.Printf("bal error: %s\n", err) - } - return nil - } - - // send sends coins to the address specified - if cmd == "send" { - err = Send(args) - if err != nil { - fmt.Printf("send error: %s\n", err) - } - return nil - } - if cmd == "fan" { - err = Fan(args) - if err != nil { - fmt.Printf("fan error: %s\n", err) - } - return nil - } - if cmd == "sweep" { - err = Sweep(args) - if err != nil { - fmt.Printf("sweep error: %s\n", err) - } - return nil - } - if cmd == "txs" { - err = Txs(args) - if err != nil { - fmt.Printf("txs error: %s\n", err) - } - return nil - } - if cmd == "blk" { - err = Blk(args) - if err != nil { - fmt.Printf("blk error: %s\n", err) - } - return nil - } - fmt.Printf("Command not recognized. type help for command list.\n") - return nil -} - -func Txs(args []string) error { - alltx, err := SCon.TS.GetAllTxs() - if err != nil { - return err - } - for i, tx := range alltx { - fmt.Printf("tx %d %s\n", i, uspv.TxToString(tx)) - } - return nil -} - -func Blk(args []string) error { - if SCon.RBytes == 0 { - return fmt.Errorf("Can't check block, spv connection broken") - } - if len(args) == 0 { - return fmt.Errorf("must specify height") - } - height, err := strconv.ParseInt(args[0], 10, 32) - if err != nil { - return err - } - - // request most recent block just to test - err = SCon.AskForOneBlock(int32(height)) - if err != nil { - return err - } - return nil -} - -// Bal prints out your score. -func Bal(args []string) error { - if SCon.TS == nil { - return fmt.Errorf("Can't get balance, spv connection broken") - } - fmt.Printf(" ----- Account Balance ----- \n") - rawUtxos, err := SCon.TS.GetAllUtxos() - if err != nil { - return err - } - var allUtxos uspv.SortableUtxoSlice - for _, utxo := range rawUtxos { - allUtxos = append(allUtxos, *utxo) - } - // smallest and unconfirmed last (because it's reversed) - sort.Sort(sort.Reverse(allUtxos)) - - var score, confScore int64 - for i, u := range allUtxos { - fmt.Printf("\tutxo %d height %d %s key:%d amt %d", - i, u.AtHeight, u.Op.String(), u.KeyIdx, u.Value) - if u.IsWit { - fmt.Printf(" WIT") - } - fmt.Printf("\n") - score += u.Value - if u.AtHeight != 0 { - confScore += u.Value - } - } - height, _ := SCon.TS.GetDBSyncHeight() - - atx, err := SCon.TS.GetAllTxs() - - stxos, err := SCon.TS.GetAllStxos() - - for i, a := range SCon.TS.Adrs { - wa, err := btcutil.NewAddressWitnessPubKeyHash( - a.PkhAdr.ScriptAddress(), Params) - if err != nil { - return err - } - fmt.Printf("address %d %s OR %s\n", i, a.PkhAdr.String(), wa.String()) - } - fmt.Printf("Total known txs: %d\n", len(atx)) - fmt.Printf("Known utxos: %d\tPreviously spent txos: %d\n", - len(allUtxos), len(stxos)) - fmt.Printf("Total coin: %d confirmed: %d\n", score, confScore) - fmt.Printf("DB sync height: %d\n", height) - return nil -} - -// Adr makes a new address. -func Adr(args []string) error { - - // if there's an arg, make 10 adrs - if len(args) > 0 { - for i := 0; i < 10; i++ { - _, err := SCon.TS.NewAdr() - if err != nil { - return err - } - } - } - if len(args) > 1 { - for i := 0; i < 1000; i++ { - _, err := SCon.TS.NewAdr() - if err != nil { - return err - } - } - } - - // always make one - a, err := SCon.TS.NewAdr() - if err != nil { - return err - } - fmt.Printf("made new address %s\n", - a.String()) - - return nil -} - -// Sweep sends every confirmed uxto in your wallet to an address. -// it does them all individually to there are a lot of txs generated. -// syntax: sweep adr -func Sweep(args []string) error { - if len(args) < 2 { - return fmt.Errorf("sweep syntax: sweep adr") - } - - adr, err := btcutil.DecodeAddress(args[0], SCon.TS.Param) - if err != nil { - fmt.Printf("error parsing %s as address\t", args[0]) - return err - } - numTxs, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - return err - } - if numTxs < 1 { - return fmt.Errorf("can't send %d txs", numTxs) - } - - rawUtxos, err := SCon.TS.GetAllUtxos() - if err != nil { - return err - } - var allUtxos uspv.SortableUtxoSlice - for _, utxo := range rawUtxos { - allUtxos = append(allUtxos, *utxo) - } - // smallest and unconfirmed last (because it's reversed) - sort.Sort(sort.Reverse(allUtxos)) - - for i, u := range allUtxos { - if u.AtHeight != 0 { - err = SCon.SendOne(allUtxos[i], adr) - if err != nil { - return err - } - numTxs-- - if numTxs == 0 { - return nil - } - } - } - - fmt.Printf("spent all confirmed utxos; not enough by %d\n", numTxs) - - return nil -} - -// Fan generates a bunch of fanout. Only for testing, can be expensive. -// syntax: fan adr numOutputs valOutputs witty -func Fan(args []string) error { - if len(args) < 3 { - return fmt.Errorf("fan syntax: fan adr numOutputs valOutputs") - } - adr, err := btcutil.DecodeAddress(args[0], SCon.TS.Param) - if err != nil { - fmt.Printf("error parsing %s as address\t", args[0]) - return err - } - numOutputs, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - return err - } - valOutputs, err := strconv.ParseInt(args[2], 10, 64) - if err != nil { - return err - } - - adrs := make([]btcutil.Address, numOutputs) - amts := make([]int64, numOutputs) - - for i := int64(0); i < numOutputs; i++ { - adrs[i] = adr - amts[i] = valOutputs + i - } - return SCon.SendCoins(adrs, amts) -} - -// Send sends coins. -func Send(args []string) error { - if SCon.RBytes == 0 { - return fmt.Errorf("Can't send, spv connection broken") - } - // get all utxos from the database - allUtxos, err := SCon.TS.GetAllUtxos() - if err != nil { - return err - } - var score int64 // score is the sum of all utxo amounts. highest score wins. - // add all the utxos up to get the score - for _, u := range allUtxos { - score += u.Value - } - - // score is 0, cannot unlock 'send coins' acheivement - if score == 0 { - return fmt.Errorf("You don't have money. Work hard.") - } - // need args, fail - if len(args) < 2 { - return fmt.Errorf("need args: ssend address amount(satoshis) wit?") - } - adr, err := btcutil.DecodeAddress(args[0], SCon.TS.Param) - if err != nil { - fmt.Printf("error parsing %s as address\t", args[0]) - return err - } - amt, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - return err - } - if amt < 1000 { - return fmt.Errorf("can't send %d, too small", amt) - } - - fmt.Printf("send %d to address: %s \n", - amt, adr.String()) - - var adrs []btcutil.Address - var amts []int64 - - adrs = append(adrs, adr) - amts = append(amts, amt) - err = SCon.SendCoins(adrs, amts) - if err != nil { - return err - } - return nil -} - -func Help(args []string) error { - fmt.Printf("commands:\n") - fmt.Printf("help adr bal send exit\n") - return nil -}