lndc: remove package as it's been superseded by brontide
This commit is contained in:
parent
4fe23a8b3e
commit
422336480e
363
lndc/conn.go
363
lndc/conn.go
@ -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)
|
|
202
lndc/listener.go
202
lndc/listener.go
@ -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()
|
|
||||||
}
|
|
191
lndc/lnadr.go
191
lndc/lnadr.go
@ -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 "<pubkey or pkh>@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)
|
|
||||||
}
|
|
@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
@ -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())
|
|
||||||
}
|
|
427
shell.go
427
shell.go
@ -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
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user