Merge branch 'protocol'

This commit is contained in:
херетик
2023-02-27 10:35:16 +00:00
45 changed files with 932 additions and 1075 deletions

View File

@@ -21,13 +21,11 @@ var (
) )
// Len is the length of the signatures used in Indra, compact keys that can have // 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 // the public key extracted from them.
// specify it in messages.
const Len = 65 const Len = 65
// Bytes is an ECDSA BIP62 formatted compact signature which allows the recovery // 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 // of the public key from the signature.
// extra bytes to also specify the public key of the signer.
type Bytes [Len]byte type Bytes [Len]byte
// Sign produces an ECDSA BIP62 compact signature. // Sign produces an ECDSA BIP62 compact signature.

View File

@@ -2,9 +2,9 @@ package hiddenservice
import ( import (
"git-indra.lan/indra-labs/indra" "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/nonce"
"git-indra.lan/indra-labs/indra/pkg/crypto/sha256" "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" "git-indra.lan/indra-labs/indra/pkg/messages/magicbytes"
log2 "git-indra.lan/indra-labs/indra/pkg/proc/log" 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/splice"
@@ -14,7 +14,7 @@ import (
const ( const (
MagicString = "hs" MagicString = "hs"
Len = magicbytes.Len + nonce.IDLen + pub.KeyLen + Len = magicbytes.Len + nonce.IDLen + intro.Len +
3*sha256.Len + nonce.IVLen*3 3*sha256.Len + nonce.IVLen*3
) )
@@ -30,11 +30,7 @@ var (
// header for any client that requests it. // header for any client that requests it.
type Layer struct { type Layer struct {
nonce.ID nonce.ID
// Identity is a public key identifying the hidden service. It is encoded intro.Layer
// 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
// Ciphers is a set of 3 symmetric ciphers that are to be used in their // Ciphers is a set of 3 symmetric ciphers that are to be used in their
// given order over the reply message from the service. // given order over the reply message from the service.
Ciphers [3]sha256.Hash Ciphers [3]sha256.Hash
@@ -56,7 +52,9 @@ func (x *Layer) Encode(b slice.Bytes, c *slice.Cursor) {
splice.Splice(b, c). splice.Splice(b, c).
Magic(Magic). Magic(Magic).
ID(x.ID). 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]). Hash(x.Ciphers[0]).Hash(x.Ciphers[1]).Hash(x.Ciphers[2]).
IV(x.Nonces[0]).IV(x.Nonces[1]).IV(x.Nonces[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). splice.Splice(b, c).
ReadID(&x.ID). 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]). ReadHash(&x.Ciphers[0]).ReadHash(&x.Ciphers[1]).ReadHash(&x.Ciphers[2]).
ReadIV(&x.Nonces[0]).ReadIV(&x.Nonces[1]).ReadIV(&x.Nonces[2]) ReadIV(&x.Nonces[0]).ReadIV(&x.Nonces[1]).ReadIV(&x.Nonces[2])
return return

View File

@@ -4,17 +4,27 @@ import (
"net" "net"
"net/netip" "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/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" "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/splice"
"git-indra.lan/indra-labs/indra/pkg/types" "git-indra.lan/indra-labs/indra/pkg/types"
"git-indra.lan/indra-labs/indra/pkg/util/slice" "git-indra.lan/indra-labs/indra/pkg/util/slice"
) )
var (
log = log2.GetLogger(indra.PathBase)
check = log.E.Chk
)
const ( const (
MagicString = "in" MagicString = "in"
AddrLen = net.IPv6len + 3 AddrLen = net.IPv6len + 3
Len = magicbytes.Len + pub.KeyLen + AddrLen Len = magicbytes.Len + pub.KeyLen + AddrLen + sig.Len
) )
var ( var (
@@ -22,19 +32,60 @@ var (
) )
type Layer struct { type Layer struct {
*pub.Key Key *pub.Key
*netip.AddrPort 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) Insert(o types.Onion) {}
func (im *Layer) Len() int { return Len } func (im *Layer) Len() int { return Len }
func (im *Layer) Encode(b slice.Bytes, c *slice.Cursor) { 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 return
} }
func (im *Layer) Decode(b slice.Bytes, c *slice.Cursor) (e error) { 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 return
} }

View File

@@ -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")
}
}

View File

@@ -44,7 +44,7 @@ var (
// is concealed to the hops except for the encryption crypt they decrypt using // is concealed to the hops except for the encryption crypt they decrypt using
// their Payload key, delivered in this message. // their Payload key, delivered in this message.
type Layer struct { type Layer struct {
nonce.ID nonce.ID // only used by a node
Hop byte // only used by a node Hop byte // only used by a node
Header, Payload *prv.Key Header, Payload *prv.Key
types.Onion types.Onion

157
pkg/relay/balance.go Normal file
View File

@@ -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)
}

View File

@@ -10,9 +10,9 @@ import (
"go.uber.org/atomic" "go.uber.org/atomic"
"git-indra.lan/indra-labs/indra/pkg/crypto/key/prv" "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/nonce"
"git-indra.lan/indra-labs/indra/pkg/crypto/sha256" "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" 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/service"
"git-indra.lan/indra-labs/indra/pkg/transport" "git-indra.lan/indra-labs/indra/pkg/transport"
@@ -290,10 +290,10 @@ func TestClient_HiddenService(t *testing.T) {
t.Error(e) t.Error(e)
t.FailNow() t.FailNow()
} }
identPub := pub.Derive(identPrv)
id := nonce.NewID() id := nonce.NewID()
il := intro.New(identPrv, clients[0].GetLocalNodeAddress())
clients[0].SendIntro(id, clients[0].Sessions[i+returns], 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") log.I.Ln("success")
}) })
} }
@@ -302,8 +302,9 @@ func TestClient_HiddenService(t *testing.T) {
v.Shutdown() v.Shutdown()
} }
} }
func TestClient_HiddenServiceBroadcast(t *testing.T) { func TestClient_HiddenServiceBroadcast(t *testing.T) {
log2.SetLogLevel(log2.Trace) log2.SetLogLevel(log2.Info)
var clients []*Engine var clients []*Engine
var e error var e error
const returns = 2 const returns = 2
@@ -340,10 +341,12 @@ func TestClient_HiddenServiceBroadcast(t *testing.T) {
t.Error(e) t.Error(e)
t.FailNow() t.FailNow()
} }
identPub := pub.Derive(identPrv) log2.SetLogLevel(log2.Trace)
// identPub := pub.Derive(identPrv)
id := nonce.NewID() id := nonce.NewID()
il := intro.New(identPrv, clients[0].GetLocalNodeAddress())
clients[0].SendIntro(id, clients[0].Sessions[returns], 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") log.I.Ln("success")
}) })
time.Sleep(time.Second * 5) time.Sleep(time.Second * 5)

140
pkg/relay/exit.go Normal file
View File

@@ -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)
}

View File

@@ -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
}
}
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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))
}
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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())
}

View File

@@ -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)
}
}

View File

@@ -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!")
}
}

View File

@@ -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")
}
}

179
pkg/relay/handlers.go Normal file
View File

@@ -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!")
}
}

View File

@@ -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
}

View File

@@ -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
})
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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++
}
}
}

View File

@@ -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)
}

View File

@@ -5,6 +5,8 @@ import (
"git-indra.lan/indra-labs/indra/pkg/crypto/key/pub" "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/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" "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() 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())
}
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -26,3 +26,15 @@ func Ping(id nonce.ID, client *Session, s Circuit,
ForwardCrypt(client, ks.Next(), n[5]). ForwardCrypt(client, ks.Next(), n[5]).
Confirmation(id, 0) 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)
}

View File

@@ -1,8 +1,42 @@
package relay package relay
import ( import "git-indra.lan/indra-labs/indra/pkg/util/cryptorand"
"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, func (sm *SessionManager) SelectHops(hops []byte,
alreadyHave Sessions) (so Sessions) { alreadyHave Sessions) (so Sessions) {

View File

@@ -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
}

View File

@@ -2,11 +2,29 @@ package relay
import ( import (
"net/netip" "net/netip"
"runtime"
"git-indra.lan/indra-labs/indra/pkg/crypto/nonce" "git-indra.lan/indra-labs/indra/pkg/crypto/nonce"
"git-indra.lan/indra-labs/indra/pkg/util/slice" "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 // SendWithOneHook is used for onions with only one confirmation hook. Usually
// as returned from PostAcctOnion this is the last, confirmation or response // as returned from PostAcctOnion this is the last, confirmation or response
// layer in an onion.Skins. // layer in an onion.Skins.

View File

@@ -2,13 +2,19 @@ package relay
import ( import (
"encoding/hex" "encoding/hex"
"fmt"
"git-indra.lan/indra-labs/lnd/lnd/lnwire" "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/prv"
"git-indra.lan/indra-labs/indra/pkg/crypto/key/pub" "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/nonce"
"git-indra.lan/indra-labs/indra/pkg/crypto/sha256" "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 // A Session keeps track of a connection session. It specifically maintains the
@@ -111,3 +117,149 @@ func (c Circuit) String() (o string) {
// Sessions are arbitrary length lists of sessions. // Sessions are arbitrary length lists of sessions.
type Sessions []*Session 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)
}

View File

@@ -17,6 +17,7 @@ import (
"git-indra.lan/indra-labs/indra/pkg/messages/forward" "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/getbalance"
"git-indra.lan/indra-labs/indra/pkg/messages/hiddenservice" "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/noop"
"git-indra.lan/indra-labs/indra/pkg/messages/response" "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/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 { pubs [3]*pub.Key, nonces [3]nonce.IV) Skins {
return append(o, &hiddenservice.Layer{ return append(o, &hiddenservice.Layer{
ID: id, ID: id,
Identity: addr, Layer: *intr,
Ciphers: GenCiphers(prvs, pubs), Ciphers: GenCiphers(prvs, pubs),
Nonces: nonces, 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 { 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. // this crypt just doesn't get added in the serialization process.
if sess.Header == nil || sess.Payload == nil { if sess.Header == nil || sess.Payload == nil {
return o return o

View File

@@ -11,6 +11,7 @@ import (
"git-indra.lan/indra-labs/indra/pkg/crypto/key/cloak" "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/prv"
"git-indra.lan/indra-labs/indra/pkg/crypto/key/pub" "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/nonce"
"git-indra.lan/indra-labs/indra/pkg/crypto/sha256" "git-indra.lan/indra-labs/indra/pkg/crypto/sha256"
"git-indra.lan/indra-labs/indra/pkg/messages/magicbytes" "git-indra.lan/indra-labs/indra/pkg/messages/magicbytes"
@@ -177,4 +178,14 @@ func (s *Splicer) Bytes(b []byte) *Splicer {
return s 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() {} func (s *Splicer) Done() {}

View File

@@ -15,7 +15,6 @@ type Sim chan slice.Bytes
func NewSim(bufs int) Sim { return make(Sim, bufs) } func NewSim(bufs int) Sim { return make(Sim, bufs) }
func (d Sim) Send(b slice.Bytes) { func (d Sim) Send(b slice.Bytes) {
log.D.Ln("sim transport sending")
d <- b d <- b
} }
func (d Sim) Receive() <-chan slice.Bytes { func (d Sim) Receive() <-chan slice.Bytes {