introquery ready to use

This commit is contained in:
херетик
2023-02-28 11:39:21 +00:00
parent e58884a8e3
commit a7ccc09265
12 changed files with 207 additions and 102 deletions

View File

@@ -28,8 +28,8 @@ func init() {
pf.StringSliceVarP(&engineRPC, "relay-control", "r",
[]string{"127.0.0.1:8339", "::1:8339"},
"address/ports for IPv4 and v6 listeners")
viper.BindPFlag("engineP2P-relay", seedCmd.PersistentFlags().Lookup("engineP2P-relay"))
viper.BindPFlag("relay-control", seedCmd.PersistentFlags().Lookup(
viper.BindPFlag("engineP2P-relay", seedCommand.PersistentFlags().Lookup("engineP2P-relay"))
viper.BindPFlag("relay-control", seedCommand.PersistentFlags().Lookup(
"relay-control"))
rootCmd.AddCommand(relayCmd)
}

View File

@@ -3,10 +3,10 @@ package main
import "github.com/spf13/cobra"
func init() {
rootCmd.AddCommand(seedCmd)
rootCmd.AddCommand(seedCommand)
}
var seedCmd = &cobra.Command{
var seedCommand = &cobra.Command{
Use: "seed",
Short: "run and manage your seed node",
Long: `run and manage your seed node`,

View File

@@ -54,7 +54,7 @@ func (x *Layer) Encode(b slice.Bytes, c *slice.Cursor) {
ID(x.ID).
Pubkey(x.Key).
AddrPort(x.AddrPort).
Signature(x.Bytes).
Signature(x.Sig).
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 +67,7 @@ func (x *Layer) Decode(b slice.Bytes, c *slice.Cursor) (e error) {
ReadID(&x.ID).
ReadPubkey(&x.Layer.Key).
ReadAddrPort(&x.Layer.AddrPort).
ReadSignature(&x.Layer.Bytes).
ReadSignature(&x.Layer.Sig).
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

View File

@@ -34,7 +34,7 @@ var (
type Layer struct {
Key *pub.Key
AddrPort *netip.AddrPort
Bytes sig.Bytes
Sig sig.Bytes
}
func New(key *prv.Key, ap *netip.AddrPort) (in *Layer) {
@@ -50,7 +50,7 @@ func New(key *prv.Key, ap *netip.AddrPort) (in *Layer) {
in = &Layer{
Key: pk,
AddrPort: ap,
Bytes: sign,
Sig: sign,
}
return
}
@@ -59,7 +59,7 @@ 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)
key, e := im.Sig.Recover(hash)
if check(e) {
return false
}
@@ -78,7 +78,7 @@ func (im *Layer) Encode(b slice.Bytes, c *slice.Cursor) {
Magic(Magic).
Pubkey(im.Key).
AddrPort(im.AddrPort).
Signature(im.Bytes)
Signature(im.Sig)
return
}
@@ -86,6 +86,6 @@ func (im *Layer) Decode(b slice.Bytes, c *slice.Cursor) (e error) {
splice.Splice(b, c).
ReadPubkey(&im.Key).
ReadAddrPort(&im.AddrPort).
ReadSignature(&im.Bytes)
ReadSignature(&im.Sig)
return
}

View File

@@ -0,0 +1,69 @@
package introquery
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/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"
)
const (
MagicString = "iq"
Len = magicbytes.Len + pub.KeyLen +
3*sha256.Len + nonce.IVLen*3
)
var (
log = log2.GetLogger(indra.PathBase)
check = log.E.Chk
Magic = slice.Bytes(MagicString)
_ types.Onion = &Layer{}
)
// Layer introquery is a request for the introduction point for a specified
// public key of a hidden service. The reply is wrapped in a routing header
// containing the full signed introduction message intro.Layer so the address is
// verifiable.
type Layer struct {
*pub.Key
// 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
// Nonces are the nonces to use with the cipher when creating the
// encryption for the reply message.
Nonces [3]nonce.IV
}
// func (x *Layer) String() string {
// return spew.Sdump(x.Port, x.Ciphers, x.Nonces, x.Bytes.ToBytes())
// }
func (x *Layer) Insert(o types.Onion) {}
func (x *Layer) Len() int {
return Len
}
func (x *Layer) Encode(b slice.Bytes, c *slice.Cursor) {
splice.Splice(b, c).
Magic(Magic).
Pubkey(x.Key).
Hash(x.Ciphers[0]).Hash(x.Ciphers[1]).Hash(x.Ciphers[2]).
IV(x.Nonces[0]).IV(x.Nonces[1]).IV(x.Nonces[2])
}
func (x *Layer) Decode(b slice.Bytes, c *slice.Cursor) (e error) {
if len(b[*c:]) < Len-magicbytes.Len {
return magicbytes.TooShort(len(b[*c:]), Len-magicbytes.Len,
string(Magic))
}
splice.Splice(b, c).
ReadPubkey(&x.Key).
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
}

View File

@@ -252,7 +252,7 @@ out:
}
func TestClient_HiddenService(t *testing.T) {
log2.SetLogLevel(log2.Trace)
log2.SetLogLevel(log2.Info)
var clients []*Engine
var e error
const returns = 2
@@ -284,7 +284,9 @@ func TestClient_HiddenService(t *testing.T) {
log.D.F("%d %s %v", i, j, clients[0].SessionCache[j])
}
}
for i := 0; i < 25; i++ {
log2.SetLogLevel(log2.Debug)
const nHiddenServices = 25
for i := 0; i < nHiddenServices; i++ {
var identPrv *prv.Key
if identPrv, e = prv.GenerateKey(); check(e) {
t.Error(e)
@@ -292,64 +294,19 @@ func TestClient_HiddenService(t *testing.T) {
}
id := nonce.NewID()
il := intro.New(identPrv, clients[0].GetLocalNodeAddress())
clients[0].SendIntro(id, clients[0].Sessions[i+returns],
il, func(id nonce.ID, b slice.Bytes) {
log.I.Ln("success")
})
clients[0].SendIntro(id, clients[0].Sessions[i+returns], il)
}
time.Sleep(time.Second / 2)
for _, v := range clients {
v.Shutdown()
}
}
func TestClient_HiddenServiceBroadcast(t *testing.T) {
log2.SetLogLevel(log2.Info)
var clients []*Engine
var e error
const returns = 2
if clients, e = CreateNMockCircuits(false, 10, returns); check(e) {
t.Error(e)
t.FailNow()
}
// Start up the clients.
for _, v := range clients {
go v.Start()
}
// Fund the client for all hops on all nodes.
var wg sync.WaitGroup
var counter atomic.Int32
for i := 0; i < 25; i++ {
log.D.Ln("buying sessions", i)
wg.Add(1)
counter.Inc()
e = clients[0].BuyNewSessions(1000000, func() {
wg.Done()
counter.Dec()
})
if check(e) {
wg.Done()
counter.Dec()
time.Sleep(time.Second)
for i, v := range clients {
if i == 0 {
continue
}
wg.Wait()
for j := range clients[0].SessionCache {
log.D.F("%d %s %v", i, j, clients[0].SessionCache[j])
if len(v.Introductions.KnownIntros) != nHiddenServices {
log.E.Ln("did not find expected", nHiddenServices, "got",
len(v.Introductions.KnownIntros))
t.FailNow()
}
}
var identPrv *prv.Key
if identPrv, e = prv.GenerateKey(); check(e) {
t.Error(e)
t.FailNow()
}
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],
il, func(id nonce.ID, b slice.Bytes) {
log.I.Ln("success")
})
time.Sleep(time.Second * 60)
for _, v := range clients {
v.Shutdown()
}
@@ -393,19 +350,29 @@ func TestClient_HiddenServiceRequest(t *testing.T) {
t.Error(e)
t.FailNow()
}
log2.SetLogLevel(log2.Trace)
log2.SetLogLevel(log2.Debug)
// identPub := pub.Derive(identPrv)
id := nonce.NewID()
il := intro.New(identPrv, clients[0].GetLocalNodeAddress())
clients[0].SendIntro(id, clients[0].Sessions[returns],
il, func(id nonce.ID, b slice.Bytes) {
log.I.Ln("success")
})
il)
// In this test environment generally every node has the intro after 1
// second.
time.Sleep(time.Second)
for _, v := range clients {
// if i == 0 {
// continue
// }
if len(v.Introductions.KnownIntros) != 1 {
log.E.Ln("did not find expected", 1, "got",
len(v.Introductions.KnownIntros))
t.FailNow()
}
}
// Now to test nodes requesting the address (even though they already know
// it).
time.Sleep(time.Second)
for _, v := range clients {
v.Shutdown()
}

View File

@@ -31,7 +31,8 @@ func (p handlemessages) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func main() {
typesList := []string{"balance", "confirm", "crypt", "delay", "dxresponse",
"exit", "forward", "getbalance", "hiddenservice", "intro", "reverse",
"exit", "forward", "getbalance", "hiddenservice", "intro", "introquery",
"reverse",
"response", "session"}
sort.Strings(typesList)
tpl := `package relay
@@ -50,6 +51,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/introquery"
"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/response"
@@ -101,6 +103,7 @@ func Peel(b slice.Bytes, c *slice.Cursor) (on types.Onion, e error) {
{"forward", true},
{"getbalance", true},
{"hiddenservice", true},
{"introquery", true},
{"intro", false},
{"reverse", false},
{"response", true},
@@ -121,6 +124,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/introquery"
"git-indra.lan/indra-labs/indra/pkg/messages/intro"
"git-indra.lan/indra-labs/indra/pkg/messages/response"
"git-indra.lan/indra-labs/indra/pkg/messages/reverse"

View File

@@ -12,6 +12,7 @@ import (
"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/introquery"
"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"
@@ -83,6 +84,13 @@ func (eng *Engine) handleMessage(b slice.Bytes, prev types.Onion) {
case *intro.Layer:
log.T.C(recLog(on, b, eng))
eng.intro(on, b, c, prev)
case *introquery.Layer:
if prev == nil {
log.E.Ln(reflect.TypeOf(on), "requests from outside? absurd!")
return
}
log.T.C(recLog(on, b, eng))
eng.introquery(on, b, c, prev)
case *response.Layer:
if prev == nil {
log.E.Ln(reflect.TypeOf(on), "requests from outside? absurd!")

View File

@@ -44,5 +44,5 @@ func (eng *Engine) hiddenservice(hs *hiddenservice.Layer, b slice.Bytes,
hs.Layer.Key.ToBase32())
eng.Introductions.AddIntro(hs.Layer.Key, b[*c:])
log.I.Ln("stored new introduction, starting broadcast")
go eng.introductionBroadcaster(&hs.Layer)
go eng.gossipIntro(&hs.Layer)
}

View File

@@ -3,11 +3,16 @@ package relay
import (
"sync"
"git-indra.lan/indra-labs/lnd/lnd/lnwire"
"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/crypt"
"git-indra.lan/indra-labs/indra/pkg/messages/intro"
"git-indra.lan/indra-labs/indra/pkg/messages/introquery"
"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"
@@ -15,8 +20,6 @@ import (
type Intros map[pub.Bytes]slice.Bytes
type NotifiedIntroducers map[pub.Bytes][]nonce.ID
type KnownIntros map[pub.Bytes]*intro.Layer
// Introductions is a map of existing known hidden service keys and the
@@ -29,14 +32,12 @@ type KnownIntros map[pub.Bytes]*intro.Layer
type Introductions struct {
sync.Mutex
Intros
NotifiedIntroducers
KnownIntros
}
func NewIntroductions() *Introductions {
return &Introductions{Intros: make(Intros),
NotifiedIntroducers: make(NotifiedIntroducers),
KnownIntros: make(KnownIntros)}
KnownIntros: make(KnownIntros)}
}
func (in *Introductions) Find(key pub.Bytes) (header slice.Bytes) {
@@ -52,7 +53,6 @@ func (in *Introductions) Delete(key pub.Bytes) (header slice.Bytes) {
in.Lock()
var ok bool
if header, ok = in.Intros[key]; ok {
// If found, the header is not to be used again.
delete(in.Intros, key)
}
in.Unlock()
@@ -67,26 +67,11 @@ func (in *Introductions) AddIntro(pk *pub.Key, header slice.Bytes) {
log.D.Ln("entry already exists for key %x", key)
} else {
in.Intros[key] = header
in.NotifiedIntroducers[key] = []nonce.ID{}
}
in.Unlock()
}
func (in *Introductions) AddNotified(nodeID nonce.ID, ident pub.Bytes) {
in.Lock()
var ok bool
if _, ok = in.NotifiedIntroducers[ident]; ok {
in.NotifiedIntroducers[ident] = append(in.NotifiedIntroducers[ident],
nodeID)
} else {
in.NotifiedIntroducers[ident] = []nonce.ID{nodeID}
}
in.Unlock()
}
func (eng *Engine) SendIntro(id nonce.ID, target *Session, intr *intro.Layer,
hook func(id nonce.ID, b slice.Bytes)) {
func (eng *Engine) SendIntro(id nonce.ID, target *Session, intr *intro.Layer) {
hops := []byte{0, 1, 2, 3, 4, 5}
s := make(Sessions, len(hops))
s[2] = target
@@ -96,10 +81,12 @@ func (eng *Engine) SendIntro(id nonce.ID, target *Session, intr *intro.Layer,
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)
eng.SendWithOneHook(c[0].AddrPort, res, func(id nonce.ID, b slice.Bytes) {
log.I.Ln("received routing header request for %s", intr.Key.ToBase32())
})
}
func (eng *Engine) introductionBroadcaster(intr *intro.Layer) {
func (eng *Engine) gossipIntro(intr *intro.Layer) {
log.D.F("propagating hidden service introduction for %x", intr.Key.ToBytes())
done := qu.T()
msg := make(slice.Bytes, intro.Len)
@@ -113,9 +100,9 @@ func (eng *Engine) introductionBroadcaster(intr *intro.Layer) {
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.
// We broadcast the received introduction to two other randomly selected
// nodes, which guarantees the entire network will see the intro at least
// once.
var cursor int
for {
select {
@@ -141,11 +128,11 @@ func (eng *Engine) intro(intr *intro.Layer, b slice.Bytes,
eng.Introductions.Lock()
if intr.Validate() {
if _, ok := eng.Introductions.KnownIntros[intr.Key.ToBytes()]; ok {
log.T.Ln("received intro we already know about")
eng.Introductions.Unlock()
return
}
log.T.F("storing intro for %s", intr.Key.ToBase32())
log.D.F("%s storing intro for %s", eng.GetLocalNodeAddress().String(),
intr.Key.ToBase32())
eng.Introductions.KnownIntros[intr.Key.ToBytes()] = intr
log.D.F("%s sending out intro to %s at %s to all known peers",
eng.GetLocalNodeAddress(), intr.Key.ToBase32(),
@@ -171,3 +158,56 @@ func (eng *Engine) intro(intr *intro.Layer, b slice.Bytes,
eng.Introductions.Unlock()
}
}
func IntroQuery(hsk *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).
IntroQuery(hsk, 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) introquery(iq *introquery.Layer, b slice.Bytes,
c *slice.Cursor, prev types.Onion) {
eng.Introductions.Lock()
var ok bool
var il *intro.Layer
if il, ok = eng.Introductions.KnownIntros[iq.Key.ToBytes()]; !ok {
// if the reply is zeroes the querant knows it needs to retry at a
// different relay
il = &intro.Layer{}
}
eng.Introductions.Unlock()
header := b[*c:c.Inc(crypt.ReverseHeaderLen)]
rb := FormatReply(header,
Encode(il), iq.Ciphers, iq.Nonces)
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, "introquery")
}
}
eng.handleMessage(rb, iq)
}

View File

@@ -15,6 +15,7 @@ import (
"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/introquery"
"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"
@@ -75,6 +76,11 @@ func Peel(b slice.Bytes, c *slice.Cursor) (on types.Onion, e error) {
if e = on.Decode(b, c); check(e) {
return
}
case introquery.MagicString:
on = &introquery.Layer{}
if e = on.Decode(b, c); check(e) {
return
}
case response.MagicString:
on = &response.Layer{}
if e = on.Decode(b, c); check(e) {

View File

@@ -18,6 +18,7 @@ import (
"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/introquery"
"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"
@@ -124,6 +125,16 @@ func (o Skins) HiddenService(id nonce.ID, intr *intro.Layer, prvs [3]*prv.Key,
})
}
func (o Skins) IntroQuery(hsk *pub.Key, prvs [3]*prv.Key, pubs [3]*pub.Key,
nonces [3]nonce.IV) Skins {
return append(o, &introquery.Layer{
Key: hsk,
Ciphers: GenCiphers(prvs, pubs),
Nonces: nonces,
})
}
func (o Skins) Reverse(ip *netip.AddrPort) Skins {
return append(o, &reverse.Layer{AddrPort: ip, Onion: nop})
}