diff --git a/pkg/crypto/key/sig/signature.go b/pkg/crypto/key/sig/signature.go index 0a75fff9..ca93b25d 100644 --- a/pkg/crypto/key/sig/signature.go +++ b/pkg/crypto/key/sig/signature.go @@ -7,7 +7,7 @@ package sig import ( "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa" - + "git-indra.lan/indra-labs/indra" "git-indra.lan/indra-labs/indra/pkg/crypto/key/prv" "git-indra.lan/indra-labs/indra/pkg/crypto/key/pub" @@ -21,13 +21,11 @@ var ( ) // Len is the length of the signatures used in Indra, compact keys that can have -// the public key extracted from them, thus eliminating the need to separately -// specify it in messages. +// the public key extracted from them. const Len = 65 // Bytes is an ECDSA BIP62 formatted compact signature which allows the recovery -// of the public key from the signature. This allows messages to avoid adding -// extra bytes to also specify the public key of the signer. +// of the public key from the signature. type Bytes [Len]byte // Sign produces an ECDSA BIP62 compact signature. diff --git a/pkg/messages/hiddenservice/hiddenservice.go b/pkg/messages/hiddenservice/hiddenservice.go index 79231e2b..3fa01da3 100644 --- a/pkg/messages/hiddenservice/hiddenservice.go +++ b/pkg/messages/hiddenservice/hiddenservice.go @@ -2,9 +2,9 @@ package hiddenservice import ( "git-indra.lan/indra-labs/indra" - "git-indra.lan/indra-labs/indra/pkg/crypto/key/pub" "git-indra.lan/indra-labs/indra/pkg/crypto/nonce" "git-indra.lan/indra-labs/indra/pkg/crypto/sha256" + "git-indra.lan/indra-labs/indra/pkg/messages/intro" "git-indra.lan/indra-labs/indra/pkg/messages/magicbytes" log2 "git-indra.lan/indra-labs/indra/pkg/proc/log" "git-indra.lan/indra-labs/indra/pkg/splice" @@ -14,7 +14,7 @@ import ( const ( MagicString = "hs" - Len = magicbytes.Len + nonce.IDLen + pub.KeyLen + + Len = magicbytes.Len + nonce.IDLen + intro.Len + 3*sha256.Len + nonce.IVLen*3 ) @@ -30,11 +30,7 @@ var ( // header for any client that requests it. type Layer struct { nonce.ID - // Identity is a public key identifying the hidden service. It is encoded - // into Bech32 encoding to function like an IP address, with a 2 byte - // truncated hash check suffix to eliminate possible human input errors and - // ending in ".indra" to indicate it is an indra hidden service. - Identity *pub.Key + intro.Layer // Ciphers is a set of 3 symmetric ciphers that are to be used in their // given order over the reply message from the service. Ciphers [3]sha256.Hash @@ -56,7 +52,9 @@ func (x *Layer) Encode(b slice.Bytes, c *slice.Cursor) { splice.Splice(b, c). Magic(Magic). ID(x.ID). - Pubkey(x.Identity). + Pubkey(x.Key). + AddrPort(x.AddrPort). + Signature(x.Bytes). Hash(x.Ciphers[0]).Hash(x.Ciphers[1]).Hash(x.Ciphers[2]). IV(x.Nonces[0]).IV(x.Nonces[1]).IV(x.Nonces[2]) } @@ -67,7 +65,9 @@ func (x *Layer) Decode(b slice.Bytes, c *slice.Cursor) (e error) { } splice.Splice(b, c). ReadID(&x.ID). - ReadPubkey(&x.Identity). + ReadPubkey(&x.Layer.Key). + ReadAddrPort(&x.Layer.AddrPort). + ReadSignature(&x.Layer.Bytes). ReadHash(&x.Ciphers[0]).ReadHash(&x.Ciphers[1]).ReadHash(&x.Ciphers[2]). ReadIV(&x.Nonces[0]).ReadIV(&x.Nonces[1]).ReadIV(&x.Nonces[2]) return diff --git a/pkg/messages/intro/intro-message.go b/pkg/messages/intro/intro-message.go index ec4b9aca..ae9e9f0c 100644 --- a/pkg/messages/intro/intro-message.go +++ b/pkg/messages/intro/intro-message.go @@ -4,17 +4,27 @@ import ( "net" "net/netip" + "git-indra.lan/indra-labs/indra" + "git-indra.lan/indra-labs/indra/pkg/crypto/key/prv" "git-indra.lan/indra-labs/indra/pkg/crypto/key/pub" + "git-indra.lan/indra-labs/indra/pkg/crypto/key/sig" + "git-indra.lan/indra-labs/indra/pkg/crypto/sha256" "git-indra.lan/indra-labs/indra/pkg/messages/magicbytes" + log2 "git-indra.lan/indra-labs/indra/pkg/proc/log" "git-indra.lan/indra-labs/indra/pkg/splice" "git-indra.lan/indra-labs/indra/pkg/types" "git-indra.lan/indra-labs/indra/pkg/util/slice" ) +var ( + log = log2.GetLogger(indra.PathBase) + check = log.E.Chk +) + const ( MagicString = "in" AddrLen = net.IPv6len + 3 - Len = magicbytes.Len + pub.KeyLen + AddrLen + Len = magicbytes.Len + pub.KeyLen + AddrLen + sig.Len ) var ( @@ -22,19 +32,60 @@ var ( ) type Layer struct { - *pub.Key - *netip.AddrPort + Key *pub.Key + AddrPort *netip.AddrPort + Bytes sig.Bytes +} + +func New(key *prv.Key, ap *netip.AddrPort) (in *Layer) { + pk := pub.Derive(key) + bap, _ := ap.MarshalBinary() + pkb := pk.ToBytes() + hash := sha256.Single(append(pkb[:], bap...)) + var e error + var sign sig.Bytes + if sign, e = sig.Sign(key, hash); check(e) { + return nil + } + in = &Layer{ + Key: pk, + AddrPort: ap, + Bytes: sign, + } + return +} + +func (im *Layer) Validate() bool { + bap, _ := im.AddrPort.MarshalBinary() + pkb := im.Key.ToBytes() + hash := sha256.Single(append(pkb[:], bap...)) + key, e := im.Bytes.Recover(hash) + if check(e) { + return false + } + kb := key.ToBytes() + if kb.Equals(pkb) { + return true + } + return false } func (im *Layer) Insert(o types.Onion) {} func (im *Layer) Len() int { return Len } func (im *Layer) Encode(b slice.Bytes, c *slice.Cursor) { - splice.Splice(b, c).Magic(Magic).Pubkey(im.Key).AddrPort(im.AddrPort) + splice.Splice(b, c). + Magic(Magic). + Pubkey(im.Key). + AddrPort(im.AddrPort). + Signature(im.Bytes) return } func (im *Layer) Decode(b slice.Bytes, c *slice.Cursor) (e error) { - splice.Splice(b, c).ReadPubkey(&im.Key).ReadAddrPort(&im.AddrPort) + splice.Splice(b, c). + ReadPubkey(&im.Key). + ReadAddrPort(&im.AddrPort). + ReadSignature(&im.Bytes) return } diff --git a/pkg/messages/intro/intro-message_test.go b/pkg/messages/intro/intro-message_test.go new file mode 100644 index 00000000..629384f0 --- /dev/null +++ b/pkg/messages/intro/intro-message_test.go @@ -0,0 +1,22 @@ +package intro + +import ( + "testing" + + "git-indra.lan/indra-labs/indra/pkg/crypto/key/prv" + "git-indra.lan/indra-labs/indra/pkg/util/slice" +) + +func TestLayer_Validate(t *testing.T) { + addr := slice.GenerateRandomAddrPortIPv4() + var e error + var idPrv *prv.Key + if idPrv, e = prv.GenerateKey(); check(e) { + return + } + im := New(idPrv, addr) + log.I.S(im) + if !im.Validate() { + t.Error("failed to validate") + } +} diff --git a/pkg/messages/session/session.go b/pkg/messages/session/session.go index 32942a53..8a3e8660 100644 --- a/pkg/messages/session/session.go +++ b/pkg/messages/session/session.go @@ -44,7 +44,7 @@ var ( // is concealed to the hops except for the encryption crypt they decrypt using // their Payload key, delivered in this message. type Layer struct { - nonce.ID + nonce.ID // only used by a node Hop byte // only used by a node Header, Payload *prv.Key types.Onion diff --git a/pkg/relay/balance.go b/pkg/relay/balance.go new file mode 100644 index 00000000..15cab409 --- /dev/null +++ b/pkg/relay/balance.go @@ -0,0 +1,157 @@ +package relay + +import ( + "git-indra.lan/indra-labs/lnd/lnd/lnwire" + + "git-indra.lan/indra-labs/indra/pkg/crypto/key/prv" + "git-indra.lan/indra-labs/indra/pkg/crypto/key/pub" + "git-indra.lan/indra-labs/indra/pkg/crypto/key/signer" + "git-indra.lan/indra-labs/indra/pkg/crypto/nonce" + "git-indra.lan/indra-labs/indra/pkg/messages/balance" + "git-indra.lan/indra-labs/indra/pkg/messages/crypt" + "git-indra.lan/indra-labs/indra/pkg/messages/getbalance" + "git-indra.lan/indra-labs/indra/pkg/types" + "git-indra.lan/indra-labs/indra/pkg/util/slice" +) + +func (eng *Engine) balance(on *balance.Layer, + b slice.Bytes, c *slice.Cursor, prev types.Onion) { + + local := eng.GetLocalNodeAddress() + pending := eng.PendingResponses.Find(on.ID) + if pending != nil { + for i := range pending.Billable { + s := eng.FindSession(pending.Billable[i]) + if s != nil { + switch { + case i < 2: + in := s.RelayRate * lnwire.MilliSatoshi( + pending.SentSize) / 1024 / 1024 + eng.DecSession(s.ID, in, true, "reverse") + case i == 2: + in := s.RelayRate * lnwire.MilliSatoshi( + pending.SentSize/2) / 1024 / 1024 + out := s.RelayRate * lnwire.MilliSatoshi( + len(b)/2) / 1024 / 1024 + eng.DecSession(s.ID, in+out, true, "getbalance") + case i > 2: + out := s.RelayRate * lnwire.MilliSatoshi( + len(b)) / 1024 / 1024 + eng.DecSession(s.ID, out, true, "reverse") + } + } + } + var se *Session + eng.IterateSessions(func(s *Session) bool { + if s.ID == on.ID { + log.D.F("%s received balance %s for session %s %s was %s", + local, + on.MilliSatoshi, on.ID, on.ConfID, s.Remaining) + se = s + return true + } + return false + }) + eng.PendingResponses.Delete(pending.ID, nil) + if se != nil { + log.D.F("got %v, expected %v", se.Remaining, on.MilliSatoshi) + se.Remaining = on.MilliSatoshi + } + } +} + +// GetBalance sends out a request in a similar way to SendExit except the node +// being queried can be any of the 5. +func GetBalance(id, confID nonce.ID, client *Session, + s Circuit, ks *signer.KeySet) Skins { + + var prvs [3]*prv.Key + for i := range prvs { + prvs[i] = ks.Next() + } + n := GenNonces(6) + var retNonces [3]nonce.IV + copy(retNonces[:], n[3:]) + var pubs [3]*pub.Key + pubs[0] = s[3].PayloadPub + pubs[1] = s[4].PayloadPub + pubs[2] = client.PayloadPub + return Skins{}. + ReverseCrypt(s[0], ks.Next(), n[0], 3). + ReverseCrypt(s[1], ks.Next(), n[1], 2). + ReverseCrypt(s[2], ks.Next(), n[2], 1). + GetBalance(id, confID, prvs, pubs, retNonces). + ReverseCrypt(s[3], prvs[0], n[3], 0). + ReverseCrypt(s[4], prvs[1], n[4], 0). + ReverseCrypt(client, prvs[2], n[5], 0) +} + +func (eng *Engine) SendGetBalance(target *Session, hook Callback) { + hops := []byte{0, 1, 2, 3, 4, 5} + s := make(Sessions, len(hops)) + s[2] = target + se := eng.SelectHops(hops, s) + var c Circuit + copy(c[:], se) + confID := nonce.NewID() + o := GetBalance(target.ID, confID, se[5], c, eng.KeySet) + log.D.Ln("sending out getbalance onion") + res := eng.PostAcctOnion(o) + eng.SendWithOneHook(c[0].AddrPort, res, hook) +} + +func (eng *Engine) getbalance(on *getbalance.Layer, + b slice.Bytes, c *slice.Cursor, prev types.Onion) { + + log.T.S(on) + var found bool + var bal *balance.Layer + eng.IterateSessions(func(s *Session) bool { + if s.ID == on.ID { + bal = &balance.Layer{ + ID: on.ID, + ConfID: on.ConfID, + MilliSatoshi: s.Remaining, + } + found = true + return true + } + return false + }) + if !found { + log.E.Ln("session not found", on.ID) + log.D.S(eng.Sessions) + return + } + header := b[*c:c.Inc(crypt.ReverseHeaderLen)] + rb := FormatReply(header, + Encode(bal), on.Ciphers, on.Nonces) + rb = append(rb, slice.NoisePad(714-len(rb))...) + switch on1 := prev.(type) { + case *crypt.Layer: + sess := eng.FindSessionByHeader(on1.ToPriv) + if sess != nil { + in := sess.RelayRate * + lnwire.MilliSatoshi(len(b)) / 2 / 1024 / 1024 + out := sess.RelayRate * + lnwire.MilliSatoshi(len(rb)) / 2 / 1024 / 1024 + eng.DecSession(sess.ID, in+out, false, "getbalance") + } + } + eng.IterateSessions(func(s *Session) bool { + if s.ID == on.ID { + bal = &balance.Layer{ + ID: on.ID, + ConfID: on.ConfID, + MilliSatoshi: s.Remaining, + } + found = true + return true + } + return false + }) + rb = FormatReply(header, + Encode(bal), on.Ciphers, on.Nonces) + rb = append(rb, slice.NoisePad(714-len(rb))...) + eng.handleMessage(rb, on) +} diff --git a/pkg/relay/engine_test.go b/pkg/relay/engine_test.go index d35c3e2a..aef8962d 100644 --- a/pkg/relay/engine_test.go +++ b/pkg/relay/engine_test.go @@ -10,9 +10,9 @@ import ( "go.uber.org/atomic" "git-indra.lan/indra-labs/indra/pkg/crypto/key/prv" - "git-indra.lan/indra-labs/indra/pkg/crypto/key/pub" "git-indra.lan/indra-labs/indra/pkg/crypto/nonce" "git-indra.lan/indra-labs/indra/pkg/crypto/sha256" + "git-indra.lan/indra-labs/indra/pkg/messages/intro" log2 "git-indra.lan/indra-labs/indra/pkg/proc/log" "git-indra.lan/indra-labs/indra/pkg/service" "git-indra.lan/indra-labs/indra/pkg/transport" @@ -290,10 +290,10 @@ func TestClient_HiddenService(t *testing.T) { t.Error(e) t.FailNow() } - identPub := pub.Derive(identPrv) id := nonce.NewID() + il := intro.New(identPrv, clients[0].GetLocalNodeAddress()) clients[0].SendIntro(id, clients[0].Sessions[i+returns], - identPub, func(id nonce.ID, b slice.Bytes) { + il, func(id nonce.ID, b slice.Bytes) { log.I.Ln("success") }) } @@ -302,8 +302,9 @@ func TestClient_HiddenService(t *testing.T) { v.Shutdown() } } + func TestClient_HiddenServiceBroadcast(t *testing.T) { - log2.SetLogLevel(log2.Trace) + log2.SetLogLevel(log2.Info) var clients []*Engine var e error const returns = 2 @@ -340,10 +341,12 @@ func TestClient_HiddenServiceBroadcast(t *testing.T) { t.Error(e) t.FailNow() } - identPub := pub.Derive(identPrv) + log2.SetLogLevel(log2.Trace) + // identPub := pub.Derive(identPrv) id := nonce.NewID() + il := intro.New(identPrv, clients[0].GetLocalNodeAddress()) clients[0].SendIntro(id, clients[0].Sessions[returns], - identPub, func(id nonce.ID, b slice.Bytes) { + il, func(id nonce.ID, b slice.Bytes) { log.I.Ln("success") }) time.Sleep(time.Second * 5) diff --git a/pkg/relay/exit.go b/pkg/relay/exit.go new file mode 100644 index 00000000..375e081b --- /dev/null +++ b/pkg/relay/exit.go @@ -0,0 +1,140 @@ +package relay + +import ( + "time" + + "git-indra.lan/indra-labs/lnd/lnd/lnwire" + + "git-indra.lan/indra-labs/indra/pkg/crypto/key/prv" + "git-indra.lan/indra-labs/indra/pkg/crypto/key/pub" + "git-indra.lan/indra-labs/indra/pkg/crypto/key/signer" + "git-indra.lan/indra-labs/indra/pkg/crypto/nonce" + "git-indra.lan/indra-labs/indra/pkg/crypto/sha256" + "git-indra.lan/indra-labs/indra/pkg/messages/crypt" + "git-indra.lan/indra-labs/indra/pkg/messages/exit" + "git-indra.lan/indra-labs/indra/pkg/messages/response" + "git-indra.lan/indra-labs/indra/pkg/types" + "git-indra.lan/indra-labs/indra/pkg/util/slice" +) + +// SendExit constructs a message containing an arbitrary payload to a node (3rd +// hop) with a set of 3 ciphers derived from the hidden PayloadPub of the return +// hops that are layered progressively after the Exit message. +// +// The Exit node forwards the packet it receives to the local port specified in +// the Exit message, and then uses the ciphers to encrypt the reply with the +// three ciphers provided, which don't enable it to decrypt the header, only to +// encrypt the payload. +// +// The response is encrypted with the given layers, the ciphers are already +// given in reverse order, so they are decoded in given order to create the +// correct payload encryption to match the PayloadPub combined with the header's +// given public From key. +// +// The header remains a constant size and each node in the Reverse trims off +// their section at the top, moves the next crypt header to the top and pads the +// remainder with noise, so it always looks like the first hop. +func SendExit(port uint16, payload slice.Bytes, id nonce.ID, + client *Session, s Circuit, ks *signer.KeySet) Skins { + + var prvs [3]*prv.Key + for i := range prvs { + prvs[i] = ks.Next() + } + n := GenNonces(6) + var returnNonces [3]nonce.IV + copy(returnNonces[:], n[3:]) + var pubs [3]*pub.Key + pubs[0] = s[3].PayloadPub + pubs[1] = s[4].PayloadPub + pubs[2] = client.PayloadPub + return Skins{}. + ReverseCrypt(s[0], ks.Next(), n[0], 3). + ReverseCrypt(s[1], ks.Next(), n[1], 2). + ReverseCrypt(s[2], ks.Next(), n[2], 1). + Exit(port, prvs, pubs, returnNonces, id, payload). + ReverseCrypt(s[3], prvs[0], n[3], 3). + ReverseCrypt(s[4], prvs[1], n[4], 2). + ReverseCrypt(client, prvs[2], n[5], 1) +} + +func (eng *Engine) exit(ex *exit.Layer, b slice.Bytes, + c *slice.Cursor, prev types.Onion) { + + // payload is forwarded to a local port and the result is forwarded + // back with a reverse header. + var e error + var result slice.Bytes + h := sha256.Single(ex.Bytes) + log.T.S(h) + log.T.F("%s received exit id %x", eng.GetLocalNodeAddress(), ex.ID) + if e = eng.SendFromLocalNode(ex.Port, ex.Bytes); check(e) { + return + } + timer := time.NewTicker(time.Second) + select { + case result = <-eng.ReceiveToLocalNode(ex.Port): + case <-timer.C: + } + // We need to wrap the result in a message crypt. + res := Encode(&response.Layer{ + ID: ex.ID, + Port: ex.Port, + Load: byte(eng.Load.Load()), + Bytes: result, + }) + rb := FormatReply(b[*c:c.Inc(crypt.ReverseHeaderLen)], + res, ex.Ciphers, ex.Nonces) + switch on := prev.(type) { + case *crypt.Layer: + sess := eng.FindSessionByHeader(on.ToPriv) + if sess == nil { + break + } + for i := range sess.Services { + if ex.Port != sess.Services[i].Port { + continue + } + in := sess.Services[i].RelayRate * + lnwire.MilliSatoshi(len(b)) / 2 / 1024 / 1024 + out := sess.Services[i].RelayRate * + lnwire.MilliSatoshi(len(rb)) / 2 / 1024 / 1024 + eng.DecSession(sess.ID, in+out, false, "exit") + break + } + } + eng.handleMessage(rb, ex) +} + +func (eng *Engine) SendExit(port uint16, msg slice.Bytes, id nonce.ID, + target *Session, hook Callback) { + + hops := []byte{0, 1, 2, 3, 4, 5} + s := make(Sessions, len(hops)) + s[2] = target + se := eng.SelectHops(hops, s) + var c Circuit + copy(c[:], se) + o := SendExit(port, msg, id, se[len(se)-1], c, eng.KeySet) + log.D.Ln("sending out exit onion") + res := eng.PostAcctOnion(o) + eng.SendWithOneHook(c[0].AddrPort, res, hook) +} + +func (eng *Engine) MakeExit(port uint16, msg slice.Bytes, id nonce.ID, + exit *Session) (c Circuit, o Skins) { + + hops := []byte{0, 1, 2, 3, 4, 5} + s := make(Sessions, len(hops)) + s[2] = exit + se := eng.SelectHops(hops, s) + copy(c[:], se) + o = SendExit(port, msg, id, se[len(se)-1], c, eng.KeySet) + return +} + +func (eng *Engine) SendExitNew(c Circuit, o Skins, hook Callback) { + log.D.Ln("sending out exit onion") + res := eng.PostAcctOnion(o) + eng.SendWithOneHook(c[0].AddrPort, res, hook) +} diff --git a/pkg/relay/helper-findcloaked.go b/pkg/relay/findcloaked.go similarity index 100% rename from pkg/relay/helper-findcloaked.go rename to pkg/relay/findcloaked.go diff --git a/pkg/relay/handler-balance.go b/pkg/relay/handler-balance.go deleted file mode 100644 index 39c11e11..00000000 --- a/pkg/relay/handler-balance.go +++ /dev/null @@ -1,55 +0,0 @@ -package relay - -import ( - "git-indra.lan/indra-labs/lnd/lnd/lnwire" - - "git-indra.lan/indra-labs/indra/pkg/messages/balance" - "git-indra.lan/indra-labs/indra/pkg/types" - "git-indra.lan/indra-labs/indra/pkg/util/slice" -) - -func (eng *Engine) balance(on *balance.Layer, - b slice.Bytes, c *slice.Cursor, prev types.Onion) { - - local := eng.GetLocalNodeAddress() - pending := eng.PendingResponses.Find(on.ID) - if pending != nil { - for i := range pending.Billable { - s := eng.FindSession(pending.Billable[i]) - if s != nil { - switch { - case i < 2: - in := s.RelayRate * lnwire.MilliSatoshi( - pending.SentSize) / 1024 / 1024 - eng.DecSession(s.ID, in, true, "reverse") - case i == 2: - in := s.RelayRate * lnwire.MilliSatoshi( - pending.SentSize/2) / 1024 / 1024 - out := s.RelayRate * lnwire.MilliSatoshi( - len(b)/2) / 1024 / 1024 - eng.DecSession(s.ID, in+out, true, "getbalance") - case i > 2: - out := s.RelayRate * lnwire.MilliSatoshi( - len(b)) / 1024 / 1024 - eng.DecSession(s.ID, out, true, "reverse") - } - } - } - var se *Session - eng.IterateSessions(func(s *Session) bool { - if s.ID == on.ID { - log.D.F("%s received balance %s for session %s %s was %s", - local, - on.MilliSatoshi, on.ID, on.ConfID, s.Remaining) - se = s - return true - } - return false - }) - eng.PendingResponses.Delete(pending.ID, nil) - if se != nil { - log.D.F("got %v, expected %v", se.Remaining, on.MilliSatoshi) - se.Remaining = on.MilliSatoshi - } - } -} diff --git a/pkg/relay/handler-confirm.go b/pkg/relay/handler-confirm.go deleted file mode 100644 index 415e9d18..00000000 --- a/pkg/relay/handler-confirm.go +++ /dev/null @@ -1,15 +0,0 @@ -package relay - -import ( - "git-indra.lan/indra-labs/indra/pkg/messages/confirm" - "git-indra.lan/indra-labs/indra/pkg/types" - "git-indra.lan/indra-labs/indra/pkg/util/slice" -) - -func (eng *Engine) confirm(on *confirm.Layer, - b slice.Bytes, c *slice.Cursor, prev types.Onion) { - - // When a confirmation arrives check if it is registered for and run the - // hook that was registered with it. - eng.PendingResponses.Delete(on.ID, nil) -} diff --git a/pkg/relay/handler-crypt.go b/pkg/relay/handler-crypt.go deleted file mode 100644 index fa496903..00000000 --- a/pkg/relay/handler-crypt.go +++ /dev/null @@ -1,32 +0,0 @@ -package relay - -import ( - "git-indra.lan/indra-labs/indra/pkg/messages/crypt" - "git-indra.lan/indra-labs/indra/pkg/messages/magicbytes" - "git-indra.lan/indra-labs/indra/pkg/messages/session" - "git-indra.lan/indra-labs/indra/pkg/types" - "git-indra.lan/indra-labs/indra/pkg/util/slice" -) - -func (eng *Engine) crypt(on *crypt.Layer, b slice.Bytes, - c *slice.Cursor, prev types.Onion) { - - // this is probably an encrypted crypt for us. - hdr, _, _, identity := eng.FindCloaked(on.Cloak) - if hdr == nil { - log.T.Ln("no matching key found from cloaked key") - return - } - on.ToPriv = hdr - on.Decrypt(hdr, b, c) - if identity { - if string(b[*c:][:magicbytes.Len]) != session.MagicString { - log.T.Ln("dropping message due to identity key with" + - " no following session") - return - } - eng.handleMessage(BudgeUp(b, *c), on) - return - } - eng.handleMessage(BudgeUp(b, *c), on) -} diff --git a/pkg/relay/handler-delay.go b/pkg/relay/handler-delay.go deleted file mode 100644 index 0102d4d1..00000000 --- a/pkg/relay/handler-delay.go +++ /dev/null @@ -1,23 +0,0 @@ -package relay - -import ( - "time" - - "git-indra.lan/indra-labs/indra/pkg/messages/delay" - "git-indra.lan/indra-labs/indra/pkg/types" - "git-indra.lan/indra-labs/indra/pkg/util/slice" -) - -func (eng *Engine) delay(on *delay.Layer, b slice.Bytes, - c *slice.Cursor, prev types.Onion) { - - // this is a message to hold the message in the buffer until a duration - // elapses. The accounting for the remainder of the message adds a - // factor to the effective byte consumption in accordance with the time - // to be stored. - // todo: accounting - select { - case <-time.After(on.Duration): - } - eng.handleMessage(BudgeUp(b, *c), on) -} diff --git a/pkg/relay/handler-exit.go b/pkg/relay/handler-exit.go deleted file mode 100644 index 4ea60a85..00000000 --- a/pkg/relay/handler-exit.go +++ /dev/null @@ -1,62 +0,0 @@ -package relay - -import ( - "time" - - "git-indra.lan/indra-labs/lnd/lnd/lnwire" - - "git-indra.lan/indra-labs/indra/pkg/crypto/sha256" - "git-indra.lan/indra-labs/indra/pkg/messages/crypt" - "git-indra.lan/indra-labs/indra/pkg/messages/exit" - "git-indra.lan/indra-labs/indra/pkg/messages/response" - "git-indra.lan/indra-labs/indra/pkg/types" - "git-indra.lan/indra-labs/indra/pkg/util/slice" -) - -func (eng *Engine) exit(ex *exit.Layer, b slice.Bytes, - c *slice.Cursor, prev types.Onion) { - - // payload is forwarded to a local port and the result is forwarded - // back with a reverse header. - var e error - var result slice.Bytes - h := sha256.Single(ex.Bytes) - log.T.S(h) - log.T.F("%s received exit id %x", eng.GetLocalNodeAddress(), ex.ID) - if e = eng.SendFromLocalNode(ex.Port, ex.Bytes); check(e) { - return - } - timer := time.NewTicker(time.Second) - select { - case result = <-eng.ReceiveToLocalNode(ex.Port): - case <-timer.C: - } - // We need to wrap the result in a message crypt. - res := Encode(&response.Layer{ - ID: ex.ID, - Port: ex.Port, - Load: byte(eng.Load.Load()), - Bytes: result, - }) - rb := FormatReply(b[*c:c.Inc(crypt.ReverseHeaderLen)], - res, ex.Ciphers, ex.Nonces) - switch on := prev.(type) { - case *crypt.Layer: - sess := eng.FindSessionByHeader(on.ToPriv) - if sess == nil { - break - } - for i := range sess.Services { - if ex.Port != sess.Services[i].Port { - continue - } - in := sess.Services[i].RelayRate * - lnwire.MilliSatoshi(len(b)) / 2 / 1024 / 1024 - out := sess.Services[i].RelayRate * - lnwire.MilliSatoshi(len(rb)) / 2 / 1024 / 1024 - eng.DecSession(sess.ID, in+out, false, "exit") - break - } - } - eng.handleMessage(rb, ex) -} diff --git a/pkg/relay/handler-forward.go b/pkg/relay/handler-forward.go deleted file mode 100644 index 4634b19c..00000000 --- a/pkg/relay/handler-forward.go +++ /dev/null @@ -1,33 +0,0 @@ -package relay - -import ( - "git-indra.lan/indra-labs/lnd/lnd/lnwire" - - "git-indra.lan/indra-labs/indra/pkg/messages/crypt" - "git-indra.lan/indra-labs/indra/pkg/messages/forward" - "git-indra.lan/indra-labs/indra/pkg/types" - "git-indra.lan/indra-labs/indra/pkg/util/slice" -) - -func (eng *Engine) forward(on *forward.Layer, b slice.Bytes, - c *slice.Cursor, prev types.Onion) { - - // forward the whole buffer received onwards. Usually there will be a - // crypt.Layer under this which will be unwrapped by the receiver. - if on.AddrPort.String() == eng.GetLocalNodeAddress().String() { - // it is for us, we want to unwrap the next part. - eng.handleMessage(BudgeUp(b, *c), on) - } else { - switch on1 := prev.(type) { - case *crypt.Layer: - sess := eng.FindSessionByHeader(on1.ToPriv) - if sess != nil { - eng.DecSession(sess.ID, - eng.GetLocalNodeRelayRate()*lnwire.MilliSatoshi(len(b))/1024/1024, - false, "forward") - } - } - // we need to forward this message onion. - eng.Send(on.AddrPort, BudgeUp(b, *c)) - } -} diff --git a/pkg/relay/handler-getbalance.go b/pkg/relay/handler-getbalance.go deleted file mode 100644 index 9580c8f9..00000000 --- a/pkg/relay/handler-getbalance.go +++ /dev/null @@ -1,67 +0,0 @@ -package relay - -import ( - "git-indra.lan/indra-labs/lnd/lnd/lnwire" - - "git-indra.lan/indra-labs/indra/pkg/messages/balance" - "git-indra.lan/indra-labs/indra/pkg/messages/crypt" - "git-indra.lan/indra-labs/indra/pkg/messages/getbalance" - "git-indra.lan/indra-labs/indra/pkg/types" - "git-indra.lan/indra-labs/indra/pkg/util/slice" -) - -func (eng *Engine) getbalance(on *getbalance.Layer, - b slice.Bytes, c *slice.Cursor, prev types.Onion) { - - log.T.S(on) - var found bool - var bal *balance.Layer - eng.IterateSessions(func(s *Session) bool { - if s.ID == on.ID { - bal = &balance.Layer{ - ID: on.ID, - ConfID: on.ConfID, - MilliSatoshi: s.Remaining, - } - found = true - return true - } - return false - }) - if !found { - log.E.Ln("session not found", on.ID) - log.D.S(eng.Sessions) - return - } - header := b[*c:c.Inc(crypt.ReverseHeaderLen)] - rb := FormatReply(header, - Encode(bal), on.Ciphers, on.Nonces) - rb = append(rb, slice.NoisePad(714-len(rb))...) - switch on1 := prev.(type) { - case *crypt.Layer: - sess := eng.FindSessionByHeader(on1.ToPriv) - if sess != nil { - in := sess.RelayRate * - lnwire.MilliSatoshi(len(b)) / 2 / 1024 / 1024 - out := sess.RelayRate * - lnwire.MilliSatoshi(len(rb)) / 2 / 1024 / 1024 - eng.DecSession(sess.ID, in+out, false, "getbalance") - } - } - eng.IterateSessions(func(s *Session) bool { - if s.ID == on.ID { - bal = &balance.Layer{ - ID: on.ID, - ConfID: on.ConfID, - MilliSatoshi: s.Remaining, - } - found = true - return true - } - return false - }) - rb = FormatReply(header, - Encode(bal), on.Ciphers, on.Nonces) - rb = append(rb, slice.NoisePad(714-len(rb))...) - eng.handleMessage(rb, on) -} diff --git a/pkg/relay/handler-hiddenservice.go b/pkg/relay/handler-hiddenservice.go deleted file mode 100644 index 3b8dcc37..00000000 --- a/pkg/relay/handler-hiddenservice.go +++ /dev/null @@ -1,17 +0,0 @@ -package relay - -import ( - "git-indra.lan/indra-labs/indra/pkg/messages/hiddenservice" - "git-indra.lan/indra-labs/indra/pkg/types" - "git-indra.lan/indra-labs/indra/pkg/util/slice" -) - -func (eng *Engine) hiddenservice(hs *hiddenservice.Layer, b slice.Bytes, - c *slice.Cursor, prev types.Onion) { - - log.D.F("%s adding introduction for key %s", eng.GetLocalNodeAddress(), - hs.Identity.ToBase32()) - eng.Introductions.AddIntro(hs.Identity, b[*c:]) - log.I.Ln("stored new introduction, starting broadcast") - go eng.hiddenserviceBroadcaster(hs.Identity) -} diff --git a/pkg/relay/handler-intro.go b/pkg/relay/handler-intro.go deleted file mode 100644 index 188c65e6..00000000 --- a/pkg/relay/handler-intro.go +++ /dev/null @@ -1,14 +0,0 @@ -package relay - -import ( - "git-indra.lan/indra-labs/indra/pkg/messages/intro" - "git-indra.lan/indra-labs/indra/pkg/types" - "git-indra.lan/indra-labs/indra/pkg/util/slice" -) - -func (eng *Engine) intro(intr *intro.Layer, b slice.Bytes, - c *slice.Cursor, prev types.Onion) { - - log.D.F("sending out intro to %s at %s to all known peers", - intr.Key.ToBase32(), intr.AddrPort.String()) -} diff --git a/pkg/relay/handler-response.go b/pkg/relay/handler-response.go deleted file mode 100644 index 52bc2360..00000000 --- a/pkg/relay/handler-response.go +++ /dev/null @@ -1,41 +0,0 @@ -package relay - -import ( - "git-indra.lan/indra-labs/lnd/lnd/lnwire" - - "git-indra.lan/indra-labs/indra/pkg/messages/response" - "git-indra.lan/indra-labs/indra/pkg/types" - "git-indra.lan/indra-labs/indra/pkg/util/slice" -) - -// response is a payload from an exit message. -func (eng *Engine) response(on *response.Layer, b slice.Bytes, - cur *slice.Cursor, prev types.Onion) { - - pending := eng.PendingResponses.Find(on.ID) - log.T.F("searching for pending ID %x", on.ID) - if pending != nil { - for i := range pending.Billable { - s := eng.FindSession(pending.Billable[i]) - if s != nil { - typ := "response" - relayRate := s.RelayRate - dataSize := len(b) - switch i { - case 0, 1: - dataSize = pending.SentSize - case 2: - for j := range s.Services { - if s.Services[j].Port == on.Port { - relayRate = s.Services[j].RelayRate / 2 - typ = "exit" - } - } - } - eng.DecSession(s.ID, relayRate*lnwire. - MilliSatoshi(dataSize)/1024/1024, true, typ) - } - } - eng.PendingResponses.Delete(on.ID, on.Bytes) - } -} diff --git a/pkg/relay/handler-reverse.go b/pkg/relay/handler-reverse.go deleted file mode 100644 index 70ba0dbf..00000000 --- a/pkg/relay/handler-reverse.go +++ /dev/null @@ -1,73 +0,0 @@ -package relay - -import ( - "git-indra.lan/indra-labs/lnd/lnd/lnwire" - - "git-indra.lan/indra-labs/indra/pkg/crypto/ciph" - "git-indra.lan/indra-labs/indra/pkg/messages/crypt" - "git-indra.lan/indra-labs/indra/pkg/messages/reverse" - "git-indra.lan/indra-labs/indra/pkg/types" - "git-indra.lan/indra-labs/indra/pkg/util/slice" -) - -func (eng *Engine) reverse(on *reverse.Layer, b slice.Bytes, - c *slice.Cursor, prev types.Onion) { - - var e error - var on2 types.Onion - if on.AddrPort.String() == eng.GetLocalNodeAddress().String() { - if on2, e = Peel(b, c); check(e) { - return - } - switch on1 := on2.(type) { - case *crypt.Layer: - start := *c - crypt.ReverseLayerLen - first := *c - second := first + crypt.ReverseLayerLen - last := second + crypt.ReverseLayerLen - log.T.Ln("searching for reverse crypt keys") - hdr, pld, _, _ := eng.FindCloaked(on1.Cloak) - if hdr == nil || pld == nil { - log.E.F("failed to find key for %s", - eng.GetLocalNodeAddress().String()) - return - } - // We need to find the PayloadPub to match. - on1.ToPriv = hdr - blk := ciph.GetBlock(on1.ToPriv, on1.FromPub) - // Decrypt using the Payload key and header nonce. - ciph.Encipher(blk, on1.Nonce, - b[*c:c.Inc(2*crypt.ReverseLayerLen)]) - blk = ciph.GetBlock(pld, on1.FromPub) - ciph.Encipher(blk, on1.Nonce, b[*c:]) - // shift the header segment upwards and pad the - // remainder. - copy(b[start:first], b[first:second]) - copy(b[first:second], b[second:last]) - copy(b[second:last], slice.NoisePad(crypt.ReverseLayerLen)) - if b[start:start+2].String() != reverse.MagicString { - // It's for us! - log.T.Ln("handling response") - eng.handleMessage(BudgeUp(b, last), on1) - break - } - sess := eng.FindSessionByHeader(hdr) - if sess != nil { - eng.DecSession(sess.ID, - eng.GetLocalNodeRelayRate()*lnwire. - MilliSatoshi(len(b))/1024/1024, false, "reverse") - eng.handleMessage(BudgeUp(b, start), on1) - } - default: - // If a reverse is not followed by an onion crypt the - // message is incorrectly formed, just drop it. - return - } - } else if prev != nil { - // we need to forward this message onion. - log.T.Ln("forwarding reverse") - eng.Send(on.AddrPort, b) - } else { - log.E.Ln("we do not forward nonsense! scoff! snort!") - } -} diff --git a/pkg/relay/handler-session.go b/pkg/relay/handler-session.go deleted file mode 100644 index 7ffea44d..00000000 --- a/pkg/relay/handler-session.go +++ /dev/null @@ -1,26 +0,0 @@ -package relay - -import ( - "git-indra.lan/indra-labs/indra/pkg/messages/session" - "git-indra.lan/indra-labs/indra/pkg/types" - "git-indra.lan/indra-labs/indra/pkg/util/slice" -) - -func (eng *Engine) session(on *session.Layer, b slice.Bytes, - c *slice.Cursor, prev types.Onion) { - - log.D.Ln(prev == nil) - log.T.F("incoming session %s", on.ID) - pi := eng.FindPendingPreimage(on.PreimageHash()) - if pi != nil { - // We need to delete this first in case somehow two such messages arrive - // at the same time, and we end up with duplicate sessions. - eng.DeletePendingPayment(pi.Preimage) - log.D.F("Adding session %s to %s", pi.ID, eng.GetLocalNodeAddress()) - eng.AddSession(NewSession(pi.ID, - eng.GetLocalNode(), pi.Amount, on.Header, on.Payload, on.Hop)) - eng.handleMessage(BudgeUp(b, *c), on) - } else { - log.E.Ln("dropping session message without payment") - } -} diff --git a/pkg/relay/handlers.go b/pkg/relay/handlers.go new file mode 100644 index 00000000..06c30c02 --- /dev/null +++ b/pkg/relay/handlers.go @@ -0,0 +1,179 @@ +package relay + +import ( + "time" + + "git-indra.lan/indra-labs/lnd/lnd/lnwire" + + "git-indra.lan/indra-labs/indra/pkg/crypto/ciph" + "git-indra.lan/indra-labs/indra/pkg/messages/confirm" + "git-indra.lan/indra-labs/indra/pkg/messages/crypt" + "git-indra.lan/indra-labs/indra/pkg/messages/delay" + "git-indra.lan/indra-labs/indra/pkg/messages/forward" + "git-indra.lan/indra-labs/indra/pkg/messages/magicbytes" + "git-indra.lan/indra-labs/indra/pkg/messages/response" + "git-indra.lan/indra-labs/indra/pkg/messages/reverse" + "git-indra.lan/indra-labs/indra/pkg/messages/session" + "git-indra.lan/indra-labs/indra/pkg/types" + "git-indra.lan/indra-labs/indra/pkg/util/slice" +) + +func (eng *Engine) confirm(on *confirm.Layer, + b slice.Bytes, c *slice.Cursor, prev types.Onion) { + + // When a confirmation arrives check if it is registered for and run the + // hook that was registered with it. + eng.PendingResponses.Delete(on.ID, nil) +} + +func (eng *Engine) crypt(on *crypt.Layer, b slice.Bytes, + c *slice.Cursor, prev types.Onion) { + + // this is probably an encrypted crypt for us. + hdr, _, _, identity := eng.FindCloaked(on.Cloak) + if hdr == nil { + log.T.Ln("no matching key found from cloaked key") + return + } + on.ToPriv = hdr + on.Decrypt(hdr, b, c) + if identity { + if string(b[*c:][:magicbytes.Len]) != session.MagicString { + log.T.Ln("dropping message due to identity key with" + + " no following session") + return + } + eng.handleMessage(BudgeUp(b, *c), on) + return + } + eng.handleMessage(BudgeUp(b, *c), on) +} + +func (eng *Engine) delay(on *delay.Layer, b slice.Bytes, + c *slice.Cursor, prev types.Onion) { + + // this is a message to hold the message in the buffer until a duration + // elapses. The accounting for the remainder of the message adds a + // factor to the effective byte consumption in accordance with the time + // to be stored. + // todo: accounting + select { + case <-time.After(on.Duration): + } + eng.handleMessage(BudgeUp(b, *c), on) +} + +func (eng *Engine) forward(on *forward.Layer, b slice.Bytes, + c *slice.Cursor, prev types.Onion) { + + // forward the whole buffer received onwards. Usually there will be a + // crypt.Layer under this which will be unwrapped by the receiver. + if on.AddrPort.String() == eng.GetLocalNodeAddress().String() { + // it is for us, we want to unwrap the next part. + eng.handleMessage(BudgeUp(b, *c), on) + } else { + switch on1 := prev.(type) { + case *crypt.Layer: + sess := eng.FindSessionByHeader(on1.ToPriv) + if sess != nil { + eng.DecSession(sess.ID, + eng.GetLocalNodeRelayRate()*lnwire.MilliSatoshi(len(b))/1024/1024, + false, "forward") + } + } + // we need to forward this message onion. + eng.Send(on.AddrPort, BudgeUp(b, *c)) + } +} + +// response is a payload from an exit message. +func (eng *Engine) response(on *response.Layer, b slice.Bytes, + cur *slice.Cursor, prev types.Onion) { + + pending := eng.PendingResponses.Find(on.ID) + log.T.F("searching for pending ID %x", on.ID) + if pending != nil { + for i := range pending.Billable { + s := eng.FindSession(pending.Billable[i]) + if s != nil { + typ := "response" + relayRate := s.RelayRate + dataSize := len(b) + switch i { + case 0, 1: + dataSize = pending.SentSize + case 2: + for j := range s.Services { + if s.Services[j].Port == on.Port { + relayRate = s.Services[j].RelayRate / 2 + typ = "exit" + } + } + } + eng.DecSession(s.ID, relayRate*lnwire.MilliSatoshi(dataSize)/1024/1024, true, typ) + } + } + eng.PendingResponses.Delete(on.ID, on.Bytes) + } +} + +func (eng *Engine) reverse(on *reverse.Layer, b slice.Bytes, + c *slice.Cursor, prev types.Onion) { + + var e error + var on2 types.Onion + if on.AddrPort.String() == eng.GetLocalNodeAddress().String() { + if on2, e = Peel(b, c); check(e) { + return + } + switch on1 := on2.(type) { + case *crypt.Layer: + start := *c - crypt.ReverseLayerLen + first := *c + second := first + crypt.ReverseLayerLen + last := second + crypt.ReverseLayerLen + log.T.Ln("searching for reverse crypt keys") + hdr, pld, _, _ := eng.FindCloaked(on1.Cloak) + if hdr == nil || pld == nil { + log.E.F("failed to find key for %s", + eng.GetLocalNodeAddress().String()) + return + } + // We need to find the PayloadPub to match. + on1.ToPriv = hdr + blk := ciph.GetBlock(on1.ToPriv, on1.FromPub) + // Decrypt using the Payload key and header nonce. + ciph.Encipher(blk, on1.Nonce, + b[*c:c.Inc(2*crypt.ReverseLayerLen)]) + blk = ciph.GetBlock(pld, on1.FromPub) + ciph.Encipher(blk, on1.Nonce, b[*c:]) + // shift the header segment upwards and pad the + // remainder. + copy(b[start:first], b[first:second]) + copy(b[first:second], b[second:last]) + copy(b[second:last], slice.NoisePad(crypt.ReverseLayerLen)) + if b[start:start+2].String() != reverse.MagicString { + // It's for us! + log.T.Ln("handling response") + eng.handleMessage(BudgeUp(b, last), on1) + break + } + sess := eng.FindSessionByHeader(hdr) + if sess != nil { + eng.DecSession(sess.ID, + eng.GetLocalNodeRelayRate()*lnwire.MilliSatoshi(len(b))/1024/1024, false, "reverse") + eng.handleMessage(BudgeUp(b, start), on1) + } + default: + // If a reverse is not followed by an onion crypt the + // message is incorrectly formed, just drop it. + return + } + } else if prev != nil { + // we need to forward this message onion. + log.T.Ln("forwarding reverse") + eng.Send(on.AddrPort, b) + } else { + log.E.Ln("we do not forward nonsense! scoff! snort!") + } +} diff --git a/pkg/relay/helper-buysessions.go b/pkg/relay/helper-buysessions.go deleted file mode 100644 index 31ea4d18..00000000 --- a/pkg/relay/helper-buysessions.go +++ /dev/null @@ -1,99 +0,0 @@ -package relay - -import ( - "fmt" - - "git-indra.lan/indra-labs/lnd/lnd/lnwire" - - "git-indra.lan/indra-labs/indra/pkg/crypto/nonce" - "git-indra.lan/indra-labs/indra/pkg/messages/session" - "git-indra.lan/indra-labs/indra/pkg/util/cryptorand" - "git-indra.lan/indra-labs/indra/pkg/util/slice" -) - -// BuyNewSessions performs the initial purchase of 5 sessions as well as adding -// different hop numbers to relays with existing sessions. Note that all 5 of -// the sessions will be paid the amount specified, not divided up. -func (eng *Engine) BuyNewSessions(amount lnwire.MilliSatoshi, - hook func()) (e error) { - - var nodes [5]*Node - nodes = eng.SessionManager.SelectUnusedCircuit() - for i := range nodes { - if nodes[i] == nil { - e = fmt.Errorf("failed to find nodes %d", i) - return - } - } - // Get a random return hop session (index 5). - var returnSession *Session - returnHops := eng.SessionManager.GetSessionsAtHop(5) - if len(returnHops) > 1 { - cryptorand.Shuffle(len(returnHops), func(i, j int) { - returnHops[i], returnHops[j] = returnHops[j], returnHops[i] - }) - } - // There must be at least one, and if there was more than one the first - // index of returnHops will be a randomly selected one. - returnSession = returnHops[0] - conf := nonce.NewID() - var s [5]*session.Layer - for i := range s { - s[i] = session.New(byte(i)) - } - var confirmChans [5]chan bool - var pendingConfirms int - for i := range nodes { - confirmChans[i] = nodes[i]. - PaymentChan.Send(amount, s[i]) - pendingConfirms++ - } - var success bool - for pendingConfirms > 0 { - // The confirmation channels will signal upon success or failure - // according to the LN payment send protocol once either the HTLCs - // confirm on the way back or the path fails. - select { - case success = <-confirmChans[0]: - if success { - pendingConfirms-- - } - case success = <-confirmChans[1]: - if success { - pendingConfirms-- - } - case success = <-confirmChans[2]: - if success { - pendingConfirms-- - } - case success = <-confirmChans[3]: - if success { - pendingConfirms-- - } - case success = <-confirmChans[4]: - if success { - pendingConfirms-- - } - } - } - // todo: handle payment failures! - o := SendKeys(conf, s, returnSession, nodes[:], eng.KeySet) - res := eng.PostAcctOnion(o) - eng.SendWithOneHook(nodes[0].AddrPort, res, func(id nonce.ID, b slice.Bytes) { - eng.SessionManager.Lock() - defer eng.SessionManager.Unlock() - var sessions [5]*Session - for i := range nodes { - log.D.F("confirming and storing session at hop %d %s for %s with"+ - " %v initial"+ - " balance", i, s[i].ID, nodes[i].AddrPort.String(), amount) - sessions[i] = NewSession(s[i].ID, nodes[i], amount, - s[i].Header, s[i].Payload, byte(i)) - eng.SessionManager.Add(sessions[i]) - eng.Sessions = append(eng.Sessions, sessions[i]) - eng.SessionManager.PendingPayments.Delete(s[i].PreimageHash()) - } - hook() - }) - return -} diff --git a/pkg/relay/helper-send.go b/pkg/relay/helper-send.go deleted file mode 100644 index 4af1ec36..00000000 --- a/pkg/relay/helper-send.go +++ /dev/null @@ -1,25 +0,0 @@ -package relay - -import ( - "net/netip" - "runtime" - - "git-indra.lan/indra-labs/indra/pkg/util/slice" -) - -// Send a message to a peer via their AddrPort. -func (eng *Engine) Send(addr *netip.AddrPort, b slice.Bytes) { - // first search if we already have the node available with connection - // open. - as := addr.String() - eng.ForEachNode(func(n *Node) bool { - if as == n.AddrPort.String() { - _, f, l, _ := runtime.Caller(1) - log.T.F("%s sending message to %v %s:%d", - eng.GetLocalNode().AddrPort.String(), addr, f, l) - n.Transport.Send(b) - return true - } - return false - }) -} diff --git a/pkg/relay/helper-sendexit.go b/pkg/relay/helper-sendexit.go deleted file mode 100644 index 821eb77b..00000000 --- a/pkg/relay/helper-sendexit.go +++ /dev/null @@ -1,39 +0,0 @@ -package relay - -import ( - "git-indra.lan/indra-labs/indra/pkg/crypto/nonce" - "git-indra.lan/indra-labs/indra/pkg/util/slice" -) - -func (eng *Engine) SendExit(port uint16, msg slice.Bytes, id nonce.ID, - target *Session, hook Callback) { - - hops := []byte{0, 1, 2, 3, 4, 5} - s := make(Sessions, len(hops)) - s[2] = target - se := eng.SelectHops(hops, s) - var c Circuit - copy(c[:], se) - o := SendExit(port, msg, id, se[len(se)-1], c, eng.KeySet) - log.D.Ln("sending out exit onion") - res := eng.PostAcctOnion(o) - eng.SendWithOneHook(c[0].AddrPort, res, hook) -} - -func (eng *Engine) MakeExit(port uint16, msg slice.Bytes, id nonce.ID, - exit *Session) (c Circuit, o Skins) { - - hops := []byte{0, 1, 2, 3, 4, 5} - s := make(Sessions, len(hops)) - s[2] = exit - se := eng.SelectHops(hops, s) - copy(c[:], se) - o = SendExit(port, msg, id, se[len(se)-1], c, eng.KeySet) - return -} - -func (eng *Engine) SendExitNew(c Circuit, o Skins, hook Callback) { - log.D.Ln("sending out exit onion") - res := eng.PostAcctOnion(o) - eng.SendWithOneHook(c[0].AddrPort, res, hook) -} diff --git a/pkg/relay/helper-sendgetbalance.go b/pkg/relay/helper-sendgetbalance.go deleted file mode 100644 index 50f1f00b..00000000 --- a/pkg/relay/helper-sendgetbalance.go +++ /dev/null @@ -1,19 +0,0 @@ -package relay - -import ( - "git-indra.lan/indra-labs/indra/pkg/crypto/nonce" -) - -func (eng *Engine) SendGetBalance(target *Session, hook Callback) { - hops := []byte{0, 1, 2, 3, 4, 5} - s := make(Sessions, len(hops)) - s[2] = target - se := eng.SelectHops(hops, s) - var c Circuit - copy(c[:], se) - confID := nonce.NewID() - o := GetBalance(target.ID, confID, se[5], c, eng.KeySet) - log.D.Ln("sending out getbalance onion") - res := eng.PostAcctOnion(o) - eng.SendWithOneHook(c[0].AddrPort, res, hook) -} diff --git a/pkg/relay/helper-sendintroduction.go b/pkg/relay/helper-sendintroduction.go deleted file mode 100644 index ae9ed60f..00000000 --- a/pkg/relay/helper-sendintroduction.go +++ /dev/null @@ -1,23 +0,0 @@ -package relay - -import ( - "git-indra.lan/indra-labs/indra/pkg/crypto/key/pub" - "git-indra.lan/indra-labs/indra/pkg/crypto/nonce" - "git-indra.lan/indra-labs/indra/pkg/util/slice" -) - -func (eng *Engine) SendIntro(id nonce.ID, target *Session, ident *pub.Key, - hook func(id nonce.ID, b slice.Bytes)) { - - log.I.Ln(target.Hop) - hops := []byte{0, 1, 2, 3, 4, 5} - s := make(Sessions, len(hops)) - s[2] = target - se := eng.SelectHops(hops, s) - var c Circuit - copy(c[:], se) - o := HiddenService(id, ident, se[len(se)-1], c, eng.KeySet) - log.D.Ln("sending out intro onion") - res := eng.PostAcctOnion(o) - eng.SendWithOneHook(c[0].AddrPort, res, hook) -} diff --git a/pkg/relay/helper-sendonion.go b/pkg/relay/helper-sendonion.go deleted file mode 100644 index 7485c988..00000000 --- a/pkg/relay/helper-sendonion.go +++ /dev/null @@ -1,104 +0,0 @@ -package relay - -import ( - "net/netip" - "time" - - "git-indra.lan/indra-labs/lnd/lnd/lnwire" - - "git-indra.lan/indra-labs/indra/pkg/crypto/nonce" - "git-indra.lan/indra-labs/indra/pkg/messages/balance" - "git-indra.lan/indra-labs/indra/pkg/messages/confirm" - "git-indra.lan/indra-labs/indra/pkg/messages/crypt" - "git-indra.lan/indra-labs/indra/pkg/messages/exit" - "git-indra.lan/indra-labs/indra/pkg/messages/forward" - "git-indra.lan/indra-labs/indra/pkg/messages/getbalance" - "git-indra.lan/indra-labs/indra/pkg/messages/reverse" - "git-indra.lan/indra-labs/indra/pkg/util/slice" -) - -func (eng *Engine) SendOnion(ap *netip.AddrPort, o Skins, - responseHook func(id nonce.ID, b slice.Bytes), timeout time.Duration) { - - if timeout == 0 { - timeout = DefaultTimeout - } - b := Encode(o.Assemble()) - var billable []nonce.ID - var ret nonce.ID - var last nonce.ID - var port uint16 - var postAcct []func() - var sessions Sessions - // do client accounting - skip := false - for i := range o { - if skip { - skip = false - continue - } - switch on := o[i].(type) { - case *crypt.Layer: - s := eng.FindSessionByHeaderPub(on.ToHeaderPub) - if s == nil { - continue - } - sessions = append(sessions, s) - // The last hop needs no accounting as it's us! - if i == len(o)-1 { - // The session used for the last hop is stored, however. - ret = s.ID - billable = append(billable, s.ID) - break - } - switch on2 := o[i+1].(type) { - case *forward.Layer: - billable = append(billable, s.ID) - postAcct = append(postAcct, - func() { - eng.DecSession(s.ID, - s.RelayRate* - lnwire.MilliSatoshi(len(b))/1024/1024, true, - "forward") - }) - case *reverse.Layer: - billable = append(billable, s.ID) - case *exit.Layer: - for j := range s.Services { - if s.Services[j].Port != on2.Port { - continue - } - port = on2.Port - postAcct = append(postAcct, - func() { - eng.DecSession(s.ID, - s.Services[j].RelayRate* - lnwire.MilliSatoshi(len(b)/2)/1024/1024, - true, "exit") - }) - break - } - billable = append(billable, s.ID) - last = on2.ID - skip = true - case *getbalance.Layer: - last = s.ID - billable = append(billable, s.ID) - skip = true - } - case *confirm.Layer: - last = on.ID - case *balance.Layer: - last = on.ID - } - } - if responseHook == nil { - responseHook = func(_ nonce.ID, _ slice.Bytes) { - log.D.Ln("nil response hook") - } - } - eng.PendingResponses.Add(last, len(b), sessions, billable, ret, port, - responseHook, postAcct) - log.T.Ln("sending out onion") - eng.Send(ap, b) -} diff --git a/pkg/relay/helper-sendping.go b/pkg/relay/helper-sendping.go deleted file mode 100644 index 984d5a64..00000000 --- a/pkg/relay/helper-sendping.go +++ /dev/null @@ -1,17 +0,0 @@ -package relay - -import ( - "git-indra.lan/indra-labs/indra/pkg/crypto/nonce" -) - -func (eng *Engine) SendPing(c Circuit, hook Callback) { - hops := []byte{0, 1, 2, 3, 4, 5} - s := make(Sessions, len(hops)) - copy(s, c[:]) - se := eng.SelectHops(hops, s) - copy(c[:], se) - confID := nonce.NewID() - o := Ping(confID, se[len(se)-1], c, eng.KeySet) - res := eng.PostAcctOnion(o) - eng.SendWithOneHook(c[0].AddrPort, res, hook) -} diff --git a/pkg/relay/hiddenservice-broadcast.go b/pkg/relay/hiddenservice-broadcast.go deleted file mode 100644 index be8d39fa..00000000 --- a/pkg/relay/hiddenservice-broadcast.go +++ /dev/null @@ -1,51 +0,0 @@ -package relay - -import ( - "time" - - "github.com/cybriq/qu" - - "git-indra.lan/indra-labs/indra/pkg/crypto/key/pub" - "git-indra.lan/indra-labs/indra/pkg/messages/intro" - "git-indra.lan/indra-labs/indra/pkg/util/cryptorand" - "git-indra.lan/indra-labs/indra/pkg/util/slice" -) - -type Referrers map[pub.Bytes][]pub.Bytes - -func (eng *Engine) hiddenserviceBroadcaster(hsk *pub.Key) { - log.D.F("propagating hidden service introduction for %x", hsk.ToBytes()) - done := qu.T() - me := eng.GetLocalNodeAddress() - intr := &intro.Layer{ - Key: hsk, AddrPort: me, - } - msg := make(slice.Bytes, intro.Len) - c := slice.NewCursor() - intr.Encode(msg, c) - nPeers := eng.NodesLen() - peerIndices := make([]int, nPeers) - for i := 0; i < nPeers; i++ { - peerIndices[i] = i - } - cryptorand.Shuffle(nPeers, func(i, j int) { - peerIndices[i], peerIndices[j] = peerIndices[j], peerIndices[i] - }) - // Since relays will also gossip this information, we will start a ticker - // that sends out the hidden service introduction once a second until it - // runs out of known relays to gossip to. - ticker := time.NewTicker(time.Second) - var cursor int - for { - select { - case <-eng.C.Wait(): - return - case <-done: - return - case <-ticker.C: - n := eng.FindNodeByIndex(peerIndices[cursor]) - n.Transport.Send(msg) - cursor++ - } - } -} diff --git a/pkg/relay/hiddenservice.go b/pkg/relay/hiddenservice.go new file mode 100644 index 00000000..7696938d --- /dev/null +++ b/pkg/relay/hiddenservice.go @@ -0,0 +1,89 @@ +package relay + +import ( + "time" + + "github.com/cybriq/qu" + + "git-indra.lan/indra-labs/indra/pkg/crypto/key/prv" + "git-indra.lan/indra-labs/indra/pkg/crypto/key/pub" + "git-indra.lan/indra-labs/indra/pkg/crypto/key/signer" + "git-indra.lan/indra-labs/indra/pkg/crypto/nonce" + "git-indra.lan/indra-labs/indra/pkg/messages/hiddenservice" + "git-indra.lan/indra-labs/indra/pkg/messages/intro" + "git-indra.lan/indra-labs/indra/pkg/types" + "git-indra.lan/indra-labs/indra/pkg/util/cryptorand" + "git-indra.lan/indra-labs/indra/pkg/util/slice" +) + +type Referrers map[pub.Bytes][]pub.Bytes + +func (eng *Engine) hiddenserviceBroadcaster(hs *intro.Layer) { + log.D.F("propagating hidden service introduction for %x", hs.Key.ToBytes()) + done := qu.T() + intr := &intro.Layer{ + Key: hs.Key, AddrPort: hs.AddrPort, Bytes: hs.Bytes, + } + msg := make(slice.Bytes, intro.Len) + c := slice.NewCursor() + intr.Encode(msg, c) + nPeers := eng.NodesLen() + peerIndices := make([]int, nPeers) + for i := 0; i < nPeers; i++ { + peerIndices[i] = i + } + cryptorand.Shuffle(nPeers, func(i, j int) { + peerIndices[i], peerIndices[j] = peerIndices[j], peerIndices[i] + }) + // Since relays will also gossip this information, we will start a ticker + // that sends out the hidden service introduction once a second until it + // runs out of known relays to gossip to. + ticker := time.NewTicker(time.Second) + var cursor int + for { + select { + case <-eng.C.Wait(): + return + case <-done: + return + case <-ticker.C: + n := eng.FindNodeByIndex(peerIndices[cursor]) + n.Transport.Send(msg) + cursor++ + } + } +} + +func HiddenService(id nonce.ID, il *intro.Layer, client *Session, s Circuit, + ks *signer.KeySet) Skins { + + var prvs [3]*prv.Key + for i := range prvs { + prvs[i] = ks.Next() + } + n := GenNonces(6) + var returnNonces [3]nonce.IV + copy(returnNonces[:], n[3:]) + var pubs [3]*pub.Key + pubs[0] = s[3].PayloadPub + pubs[1] = s[4].PayloadPub + pubs[2] = client.PayloadPub + return Skins{}. + ReverseCrypt(s[0], ks.Next(), n[0], 3). + ReverseCrypt(s[1], ks.Next(), n[1], 2). + ReverseCrypt(s[2], ks.Next(), n[2], 1). + HiddenService(id, il, prvs, pubs, returnNonces). + ReverseCrypt(s[3], prvs[0], n[3], 3). + ReverseCrypt(s[4], prvs[1], n[4], 2). + ReverseCrypt(client, prvs[2], n[5], 1) +} + +func (eng *Engine) hiddenservice(hs *hiddenservice.Layer, b slice.Bytes, + c *slice.Cursor, prev types.Onion) { + + log.D.F("%s adding introduction for key %s", eng.GetLocalNodeAddress(), + hs.Layer.Key.ToBase32()) + eng.Introductions.AddIntro(hs.Layer.Key, b[*c:]) + log.I.Ln("stored new introduction, starting broadcast") + go eng.hiddenserviceBroadcaster(&hs.Layer) +} diff --git a/pkg/relay/introductions.go b/pkg/relay/introductions.go index 87569f33..fb317ab8 100644 --- a/pkg/relay/introductions.go +++ b/pkg/relay/introductions.go @@ -5,6 +5,8 @@ import ( "git-indra.lan/indra-labs/indra/pkg/crypto/key/pub" "git-indra.lan/indra-labs/indra/pkg/crypto/nonce" + "git-indra.lan/indra-labs/indra/pkg/messages/intro" + "git-indra.lan/indra-labs/indra/pkg/types" "git-indra.lan/indra-labs/indra/pkg/util/slice" ) @@ -65,3 +67,27 @@ func (in *Introductions) AddNotified(nodeID nonce.ID, ident pub.Bytes) { } in.Unlock() } + +func (eng *Engine) SendIntro(id nonce.ID, target *Session, intr *intro.Layer, + hook func(id nonce.ID, b slice.Bytes)) { + + hops := []byte{0, 1, 2, 3, 4, 5} + s := make(Sessions, len(hops)) + s[2] = target + se := eng.SelectHops(hops, s) + var c Circuit + copy(c[:], se) + o := HiddenService(id, intr, se[len(se)-1], c, eng.KeySet) + log.D.Ln("sending out intro onion") + res := eng.PostAcctOnion(o) + eng.SendWithOneHook(c[0].AddrPort, res, hook) +} + +func (eng *Engine) intro(intr *intro.Layer, b slice.Bytes, + c *slice.Cursor, prev types.Onion) { + + if intr.Validate() { + log.D.F("sending out intro to %s at %s to all known peers", + intr.Key.ToBase32(), intr.AddrPort.String()) + } +} diff --git a/pkg/relay/onion-getbalance.go b/pkg/relay/onion-getbalance.go deleted file mode 100644 index 48909937..00000000 --- a/pkg/relay/onion-getbalance.go +++ /dev/null @@ -1,34 +0,0 @@ -package relay - -import ( - "git-indra.lan/indra-labs/indra/pkg/crypto/key/prv" - "git-indra.lan/indra-labs/indra/pkg/crypto/key/pub" - "git-indra.lan/indra-labs/indra/pkg/crypto/key/signer" - "git-indra.lan/indra-labs/indra/pkg/crypto/nonce" -) - -// GetBalance sends out a request in a similar way to SendExit except the node -// being queried can be any of the 5. -func GetBalance(id, confID nonce.ID, client *Session, - s Circuit, ks *signer.KeySet) Skins { - - var prvs [3]*prv.Key - for i := range prvs { - prvs[i] = ks.Next() - } - n := GenNonces(6) - var retNonces [3]nonce.IV - copy(retNonces[:], n[3:]) - var pubs [3]*pub.Key - pubs[0] = s[3].PayloadPub - pubs[1] = s[4].PayloadPub - pubs[2] = client.PayloadPub - return Skins{}. - ReverseCrypt(s[0], ks.Next(), n[0], 3). - ReverseCrypt(s[1], ks.Next(), n[1], 2). - ReverseCrypt(s[2], ks.Next(), n[2], 1). - GetBalance(id, confID, prvs, pubs, retNonces). - ReverseCrypt(s[3], prvs[0], n[3], 0). - ReverseCrypt(s[4], prvs[1], n[4], 0). - ReverseCrypt(client, prvs[2], n[5], 0) -} diff --git a/pkg/relay/onion-hiddenservice.go b/pkg/relay/onion-hiddenservice.go deleted file mode 100644 index 6497f25f..00000000 --- a/pkg/relay/onion-hiddenservice.go +++ /dev/null @@ -1,32 +0,0 @@ -package relay - -import ( - "git-indra.lan/indra-labs/indra/pkg/crypto/key/prv" - "git-indra.lan/indra-labs/indra/pkg/crypto/key/pub" - "git-indra.lan/indra-labs/indra/pkg/crypto/key/signer" - "git-indra.lan/indra-labs/indra/pkg/crypto/nonce" -) - -func HiddenService(id nonce.ID, ident *pub.Key, client *Session, s Circuit, - ks *signer.KeySet) Skins { - - var prvs [3]*prv.Key - for i := range prvs { - prvs[i] = ks.Next() - } - n := GenNonces(6) - var returnNonces [3]nonce.IV - copy(returnNonces[:], n[3:]) - var pubs [3]*pub.Key - pubs[0] = s[3].PayloadPub - pubs[1] = s[4].PayloadPub - pubs[2] = client.PayloadPub - return Skins{}. - ReverseCrypt(s[0], ks.Next(), n[0], 3). - ReverseCrypt(s[1], ks.Next(), n[1], 2). - ReverseCrypt(s[2], ks.Next(), n[2], 1). - HiddenService(id, ident, prvs, pubs, returnNonces). - ReverseCrypt(s[3], prvs[0], n[3], 3). - ReverseCrypt(s[4], prvs[1], n[4], 2). - ReverseCrypt(client, prvs[2], n[5], 1) -} diff --git a/pkg/relay/onion-sendexit.go b/pkg/relay/onion-sendexit.go deleted file mode 100644 index dab569cc..00000000 --- a/pkg/relay/onion-sendexit.go +++ /dev/null @@ -1,50 +0,0 @@ -package relay - -import ( - "git-indra.lan/indra-labs/indra/pkg/crypto/key/prv" - "git-indra.lan/indra-labs/indra/pkg/crypto/key/pub" - "git-indra.lan/indra-labs/indra/pkg/crypto/key/signer" - "git-indra.lan/indra-labs/indra/pkg/crypto/nonce" - "git-indra.lan/indra-labs/indra/pkg/util/slice" -) - -// SendExit constructs a message containing an arbitrary payload to a node (3rd -// hop) with a set of 3 ciphers derived from the hidden PayloadPub of the return -// hops that are layered progressively after the Exit message. -// -// The Exit node forwards the packet it receives to the local port specified in -// the Exit message, and then uses the ciphers to encrypt the reply with the -// three ciphers provided, which don't enable it to decrypt the header, only to -// encrypt the payload. -// -// The response is encrypted with the given layers, the ciphers are already -// given in reverse order, so they are decoded in given order to create the -// correct payload encryption to match the PayloadPub combined with the header's -// given public From key. -// -// The header remains a constant size and each node in the Reverse trims off -// their section at the top, moves the next crypt header to the top and pads the -// remainder with noise, so it always looks like the first hop. -func SendExit(port uint16, payload slice.Bytes, id nonce.ID, - client *Session, s Circuit, ks *signer.KeySet) Skins { - - var prvs [3]*prv.Key - for i := range prvs { - prvs[i] = ks.Next() - } - n := GenNonces(6) - var returnNonces [3]nonce.IV - copy(returnNonces[:], n[3:]) - var pubs [3]*pub.Key - pubs[0] = s[3].PayloadPub - pubs[1] = s[4].PayloadPub - pubs[2] = client.PayloadPub - return Skins{}. - ReverseCrypt(s[0], ks.Next(), n[0], 3). - ReverseCrypt(s[1], ks.Next(), n[1], 2). - ReverseCrypt(s[2], ks.Next(), n[2], 1). - Exit(port, prvs, pubs, returnNonces, id, payload). - ReverseCrypt(s[3], prvs[0], n[3], 3). - ReverseCrypt(s[4], prvs[1], n[4], 2). - ReverseCrypt(client, prvs[2], n[5], 1) -} diff --git a/pkg/relay/onion-sendkeys.go b/pkg/relay/onion-sendkeys.go deleted file mode 100644 index cc05ffe5..00000000 --- a/pkg/relay/onion-sendkeys.go +++ /dev/null @@ -1,47 +0,0 @@ -package relay - -import ( - "git-indra.lan/indra-labs/indra/pkg/crypto/key/signer" - "git-indra.lan/indra-labs/indra/pkg/crypto/nonce" - "git-indra.lan/indra-labs/indra/pkg/messages/session" -) - -// SendKeys provides a pair of private keys that will be used to generate the -// Purchase header bytes and to generate the ciphers provided in the Purchase -// message to encrypt the Session that is returned. -// -// The OnionSkin key, its cloaked public key counterpart used in the ToHeaderPub -// field of the Purchase message preformed header bytes, but the Ciphers -// provided in the Purchase message, for encrypting the Session to be returned, -// uses the Payload key, along with the public key found in the encrypted crypt -// of the header for the Reverse relay. -// -// This message's last crypt is a Confirmation, which allows the client to know -// that the keys were successfully delivered. -// -// This is the only onion that uses the node identity keys. The payment preimage -// hash must be available or the relay should not forward the remainder of the -// packet. -// -// If hdr/pld cipher keys are nil there must be a HeaderPub available on the -// session for the hop. This allows this function to send keys to any number of -// hops, but the very first SendKeys must have all in order to create the first -// set of sessions. This is by way of indicating to not use the IdentityPub but -// the HeaderPub instead. Not allowing free relay at all prevents spam attacks. -func SendKeys(id nonce.ID, s [5]*session.Layer, - client *Session, hop []*Node, ks *signer.KeySet) Skins { - - n := GenNonces(6) - sk := Skins{} - for i := range s { - if i == 0 { - sk = sk.Crypt(hop[i].IdentityPub, nil, ks.Next(), - n[i], 0).Session(s[i]) - } else { - sk = sk.ForwardSession(hop[i], ks.Next(), n[i], s[i]) - } - } - return sk. - ForwardCrypt(client, ks.Next(), n[5]). - Confirmation(id, 0) -} diff --git a/pkg/relay/onion-ping.go b/pkg/relay/ping.go similarity index 74% rename from pkg/relay/onion-ping.go rename to pkg/relay/ping.go index e43e8fe6..852465b1 100644 --- a/pkg/relay/onion-ping.go +++ b/pkg/relay/ping.go @@ -26,3 +26,15 @@ func Ping(id nonce.ID, client *Session, s Circuit, ForwardCrypt(client, ks.Next(), n[5]). Confirmation(id, 0) } + +func (eng *Engine) SendPing(c Circuit, hook Callback) { + hops := []byte{0, 1, 2, 3, 4, 5} + s := make(Sessions, len(hops)) + copy(s, c[:]) + se := eng.SelectHops(hops, s) + copy(c[:], se) + confID := nonce.NewID() + o := Ping(confID, se[len(se)-1], c, eng.KeySet) + res := eng.PostAcctOnion(o) + eng.SendWithOneHook(c[0].AddrPort, res, hook) +} diff --git a/pkg/relay/helper-postacct.go b/pkg/relay/postacct.go similarity index 100% rename from pkg/relay/helper-postacct.go rename to pkg/relay/postacct.go diff --git a/pkg/relay/selecthops.go b/pkg/relay/select.go similarity index 56% rename from pkg/relay/selecthops.go rename to pkg/relay/select.go index e6e4e061..20766e53 100644 --- a/pkg/relay/selecthops.go +++ b/pkg/relay/select.go @@ -1,12 +1,46 @@ package relay -import ( - "git-indra.lan/indra-labs/indra/pkg/util/cryptorand" -) +import "git-indra.lan/indra-labs/indra/pkg/util/cryptorand" + +// SelectUnusedCircuit accepts an array of 5 Node entries where all or some are +// empty and picks nodes for the remainder that do not have a hop at that +// position. +func (sm *SessionManager) SelectUnusedCircuit() (c [5]*Node) { + sm.Lock() + defer sm.Unlock() + // Create a shuffled slice of Nodes to randomise the selection process. + nodeList := make([]*Node, len(sm.nodes)-1) + copy(nodeList, sm.nodes[1:]) + for i := range nodeList { + if _, ok := sm.SessionCache[nodeList[i].ID]; !ok { + log.T.F("adding session cache entry for node %s", nodeList[i].ID) + sm.SessionCache[nodeList[i].ID] = &Circuit{} + } + } + var counter int +out: + for counter < 5 { + for i := range sm.SessionCache { + if counter == 5 { + break out + } + if sm.SessionCache[i][counter] == nil { + for j := range nodeList { + if nodeList[j].ID == i { + c[counter] = nodeList[j] + counter++ + break + } + } + } + } + } + return +} func (sm *SessionManager) SelectHops(hops []byte, alreadyHave Sessions) (so Sessions) { - + sm.Lock() defer sm.Unlock() ws := make(Sessions, 0) diff --git a/pkg/relay/selectunusedcircuit.go b/pkg/relay/selectunusedcircuit.go deleted file mode 100644 index 6fd41d28..00000000 --- a/pkg/relay/selectunusedcircuit.go +++ /dev/null @@ -1,37 +0,0 @@ -package relay - -// SelectUnusedCircuit accepts an array of 5 Node entries where all or some are -// empty and picks nodes for the remainder that do not have a hop at that -// position. -func (sm *SessionManager) SelectUnusedCircuit() (c [5]*Node) { - sm.Lock() - defer sm.Unlock() - // Create a shuffled slice of Nodes to randomise the selection process. - nodeList := make([]*Node, len(sm.nodes)-1) - copy(nodeList, sm.nodes[1:]) - for i := range nodeList { - if _, ok := sm.SessionCache[nodeList[i].ID]; !ok { - log.T.F("adding session cache entry for node %s", nodeList[i].ID) - sm.SessionCache[nodeList[i].ID] = &Circuit{} - } - } - var counter int -out: - for counter < 5 { - for i := range sm.SessionCache { - if counter == 5 { - break out - } - if sm.SessionCache[i][counter] == nil { - for j := range nodeList { - if nodeList[j].ID == i { - c[counter] = nodeList[j] - counter++ - break - } - } - } - } - } - return -} diff --git a/pkg/relay/senders.go b/pkg/relay/send.go similarity index 59% rename from pkg/relay/senders.go rename to pkg/relay/send.go index b742b158..6b97173d 100644 --- a/pkg/relay/senders.go +++ b/pkg/relay/send.go @@ -2,11 +2,29 @@ package relay import ( "net/netip" + "runtime" "git-indra.lan/indra-labs/indra/pkg/crypto/nonce" "git-indra.lan/indra-labs/indra/pkg/util/slice" ) +// Send a message to a peer via their AddrPort. +func (eng *Engine) Send(addr *netip.AddrPort, b slice.Bytes) { + // first search if we already have the node available with connection + // open. + as := addr.String() + eng.ForEachNode(func(n *Node) bool { + if as == n.AddrPort.String() { + _, f, l, _ := runtime.Caller(1) + log.T.F("%s sending message to %v %s:%d", + eng.GetLocalNode().AddrPort.String(), addr, f, l) + n.Transport.Send(b) + return true + } + return false + }) +} + // SendWithOneHook is used for onions with only one confirmation hook. Usually // as returned from PostAcctOnion this is the last, confirmation or response // layer in an onion.Skins. diff --git a/pkg/relay/session.go b/pkg/relay/session.go index 669e68ca..e70beb6b 100644 --- a/pkg/relay/session.go +++ b/pkg/relay/session.go @@ -2,13 +2,19 @@ package relay import ( "encoding/hex" - + "fmt" + "git-indra.lan/indra-labs/lnd/lnd/lnwire" - + "git-indra.lan/indra-labs/indra/pkg/crypto/key/prv" "git-indra.lan/indra-labs/indra/pkg/crypto/key/pub" + "git-indra.lan/indra-labs/indra/pkg/crypto/key/signer" "git-indra.lan/indra-labs/indra/pkg/crypto/nonce" "git-indra.lan/indra-labs/indra/pkg/crypto/sha256" + "git-indra.lan/indra-labs/indra/pkg/messages/session" + "git-indra.lan/indra-labs/indra/pkg/types" + "git-indra.lan/indra-labs/indra/pkg/util/cryptorand" + "git-indra.lan/indra-labs/indra/pkg/util/slice" ) // A Session keeps track of a connection session. It specifically maintains the @@ -35,7 +41,7 @@ func NewSession( pldPrv *prv.Key, hop byte, ) (s *Session) { - + var e error if hdrPrv == nil || pldPrv == nil { if hdrPrv, e = prv.GenerateKey(); check(e) { @@ -111,3 +117,149 @@ func (c Circuit) String() (o string) { // Sessions are arbitrary length lists of sessions. type Sessions []*Session + +func (eng *Engine) session(on *session.Layer, b slice.Bytes, + c *slice.Cursor, prev types.Onion) { + + log.D.Ln(prev == nil) + log.T.F("incoming session %x", on.PreimageHash()) + pi := eng.FindPendingPreimage(on.PreimageHash()) + if pi != nil { + // We need to delete this first in case somehow two such messages arrive + // at the same time, and we end up with duplicate sessions. + eng.DeletePendingPayment(pi.Preimage) + log.D.F("Adding session %s to %s", pi.ID, eng.GetLocalNodeAddress()) + eng.AddSession(NewSession(pi.ID, + eng.GetLocalNode(), pi.Amount, on.Header, on.Payload, on.Hop)) + eng.handleMessage(BudgeUp(b, *c), on) + } else { + log.E.Ln("dropping session message without payment") + } +} + +// BuyNewSessions performs the initial purchase of 5 sessions as well as adding +// different hop numbers to relays with existing sessions. Note that all 5 of +// the sessions will be paid the amount specified, not divided up. +func (eng *Engine) BuyNewSessions(amount lnwire.MilliSatoshi, + hook func()) (e error) { + + var nodes [5]*Node + nodes = eng.SessionManager.SelectUnusedCircuit() + for i := range nodes { + if nodes[i] == nil { + e = fmt.Errorf("failed to find nodes %d", i) + return + } + } + // Get a random return hop session (index 5). + var returnSession *Session + returnHops := eng.SessionManager.GetSessionsAtHop(5) + if len(returnHops) > 1 { + cryptorand.Shuffle(len(returnHops), func(i, j int) { + returnHops[i], returnHops[j] = returnHops[j], returnHops[i] + }) + } + // There must be at least one, and if there was more than one the first + // index of returnHops will be a randomly selected one. + returnSession = returnHops[0] + conf := nonce.NewID() + var s [5]*session.Layer + for i := range s { + s[i] = session.New(byte(i)) + } + var confirmChans [5]chan bool + var pendingConfirms int + for i := range nodes { + confirmChans[i] = nodes[i]. + PaymentChan.Send(amount, s[i]) + pendingConfirms++ + } + var success bool + for pendingConfirms > 0 { + // The confirmation channels will signal upon success or failure + // according to the LN payment send protocol once either the HTLCs + // confirm on the way back or the path fails. + select { + case success = <-confirmChans[0]: + if success { + pendingConfirms-- + } + case success = <-confirmChans[1]: + if success { + pendingConfirms-- + } + case success = <-confirmChans[2]: + if success { + pendingConfirms-- + } + case success = <-confirmChans[3]: + if success { + pendingConfirms-- + } + case success = <-confirmChans[4]: + if success { + pendingConfirms-- + } + } + } + // todo: handle payment failures! + o := SendSessions(conf, s, returnSession, nodes[:], eng.KeySet) + res := eng.PostAcctOnion(o) + eng.SendWithOneHook(nodes[0].AddrPort, res, func(id nonce.ID, b slice.Bytes) { + eng.SessionManager.Lock() + defer eng.SessionManager.Unlock() + var sessions [5]*Session + for i := range nodes { + log.D.F("confirming and storing session at hop %d %s for %s with"+ + " %v initial"+ + " balance", i, s[i].ID, nodes[i].AddrPort.String(), amount) + sessions[i] = NewSession(s[i].ID, nodes[i], amount, + s[i].Header, s[i].Payload, byte(i)) + eng.SessionManager.Add(sessions[i]) + eng.Sessions = append(eng.Sessions, sessions[i]) + eng.SessionManager.PendingPayments.Delete(s[i].PreimageHash()) + } + hook() + }) + return +} + +// SendSessions provides a pair of private keys that will be used to generate the +// Purchase header bytes and to generate the ciphers provided in the Purchase +// message to encrypt the Session that is returned. +// +// The OnionSkin key, its cloaked public key counterpart used in the ToHeaderPub +// field of the Purchase message preformed header bytes, but the Ciphers +// provided in the Purchase message, for encrypting the Session to be returned, +// uses the Payload key, along with the public key found in the encrypted crypt +// of the header for the Reverse relay. +// +// This message's last crypt is a Confirmation, which allows the client to know +// that the keys were successfully delivered. +// +// This is the only onion that uses the node identity keys. The payment preimage +// hash must be available or the relay should not forward the remainder of the +// packet. +// +// If hdr/pld cipher keys are nil there must be a HeaderPub available on the +// session for the hop. This allows this function to send keys to any number of +// hops, but the very first SendSessions must have all in order to create the first +// set of sessions. This is by way of indicating to not use the IdentityPub but +// the HeaderPub instead. Not allowing free relay at all prevents spam attacks. +func SendSessions(id nonce.ID, s [5]*session.Layer, + client *Session, hop []*Node, ks *signer.KeySet) Skins { + + n := GenNonces(6) + sk := Skins{} + for i := range s { + if i == 0 { + sk = sk.Crypt(hop[i].IdentityPub, nil, ks.Next(), + n[i], 0).Session(s[i]) + } else { + sk = sk.ForwardSession(hop[i], ks.Next(), n[i], s[i]) + } + } + return sk. + ForwardCrypt(client, ks.Next(), n[5]). + Confirmation(id, 0) +} diff --git a/pkg/relay/skins.go b/pkg/relay/skins.go index 51c9c455..dc4e9712 100644 --- a/pkg/relay/skins.go +++ b/pkg/relay/skins.go @@ -17,6 +17,7 @@ import ( "git-indra.lan/indra-labs/indra/pkg/messages/forward" "git-indra.lan/indra-labs/indra/pkg/messages/getbalance" "git-indra.lan/indra-labs/indra/pkg/messages/hiddenservice" + "git-indra.lan/indra-labs/indra/pkg/messages/intro" "git-indra.lan/indra-labs/indra/pkg/messages/noop" "git-indra.lan/indra-labs/indra/pkg/messages/response" "git-indra.lan/indra-labs/indra/pkg/messages/reverse" @@ -113,13 +114,13 @@ func (o Skins) GetBalance(id, confID nonce.ID, prvs [3]*prv.Key, }) } -func (o Skins) HiddenService(id nonce.ID, addr *pub.Key, prvs [3]*prv.Key, +func (o Skins) HiddenService(id nonce.ID, intr *intro.Layer, prvs [3]*prv.Key, pubs [3]*pub.Key, nonces [3]nonce.IV) Skins { return append(o, &hiddenservice.Layer{ - ID: id, - Identity: addr, - Ciphers: GenCiphers(prvs, pubs), - Nonces: nonces, + ID: id, + Layer: *intr, + Ciphers: GenCiphers(prvs, pubs), + Nonces: nonces, }) } @@ -133,7 +134,7 @@ func (o Skins) Response(id nonce.ID, res slice.Bytes, port uint16) Skins { } func (o Skins) Session(sess *session.Layer) Skins { - // SendKeys can apply to from 1 to 5 nodes, if either key is nil then + // SendSessions can apply to from 1 to 5 nodes, if either key is nil then // this crypt just doesn't get added in the serialization process. if sess.Header == nil || sess.Payload == nil { return o diff --git a/pkg/splice/splice.go b/pkg/splice/splice.go index b4987389..fc815da4 100644 --- a/pkg/splice/splice.go +++ b/pkg/splice/splice.go @@ -11,6 +11,7 @@ import ( "git-indra.lan/indra-labs/indra/pkg/crypto/key/cloak" "git-indra.lan/indra-labs/indra/pkg/crypto/key/prv" "git-indra.lan/indra-labs/indra/pkg/crypto/key/pub" + "git-indra.lan/indra-labs/indra/pkg/crypto/key/sig" "git-indra.lan/indra-labs/indra/pkg/crypto/nonce" "git-indra.lan/indra-labs/indra/pkg/crypto/sha256" "git-indra.lan/indra-labs/indra/pkg/messages/magicbytes" @@ -177,4 +178,14 @@ func (s *Splicer) Bytes(b []byte) *Splicer { return s } +func (s *Splicer) Signature(sb sig.Bytes) *Splicer { + copy(s.b[*s.c:s.c.Inc(sig.Len)], sb[:]) + return s +} + +func (s *Splicer) ReadSignature(sb *sig.Bytes) *Splicer { + copy(sb[:], s.b[*s.c:s.c.Inc(sig.Len)]) + return s +} + func (s *Splicer) Done() {} diff --git a/pkg/transport/sim.go b/pkg/transport/sim.go index 5b766a1d..f561971e 100644 --- a/pkg/transport/sim.go +++ b/pkg/transport/sim.go @@ -15,7 +15,6 @@ type Sim chan slice.Bytes func NewSim(bufs int) Sim { return make(Sim, bufs) } func (d Sim) Send(b slice.Bytes) { - log.D.Ln("sim transport sending") d <- b } func (d Sim) Receive() <-chan slice.Bytes {