diff --git a/cmd/indra/relay.go b/cmd/indra/relay.go index cac72090..13378c1b 100644 --- a/cmd/indra/relay.go +++ b/cmd/indra/relay.go @@ -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) } diff --git a/cmd/indra/seed.go b/cmd/indra/seed.go index 86d849b3..3613d46d 100644 --- a/cmd/indra/seed.go +++ b/cmd/indra/seed.go @@ -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`, diff --git a/pkg/messages/hiddenservice/hiddenservice.go b/pkg/messages/hiddenservice/hiddenservice.go index 3fa01da3..07c147a9 100644 --- a/pkg/messages/hiddenservice/hiddenservice.go +++ b/pkg/messages/hiddenservice/hiddenservice.go @@ -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 diff --git a/pkg/messages/intro/intro-message.go b/pkg/messages/intro/intro-message.go index ae9e9f0c..fba0e0b6 100644 --- a/pkg/messages/intro/intro-message.go +++ b/pkg/messages/intro/intro-message.go @@ -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 } diff --git a/pkg/messages/introquery/introquery.go b/pkg/messages/introquery/introquery.go new file mode 100644 index 00000000..9846edbb --- /dev/null +++ b/pkg/messages/introquery/introquery.go @@ -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 +} diff --git a/pkg/relay/engine_test.go b/pkg/relay/engine_test.go index 07e8991e..5554f14e 100644 --- a/pkg/relay/engine_test.go +++ b/pkg/relay/engine_test.go @@ -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() } diff --git a/pkg/relay/gen/main.go b/pkg/relay/gen/main.go index b675325a..7cc5d1d8 100644 --- a/pkg/relay/gen/main.go +++ b/pkg/relay/gen/main.go @@ -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" diff --git a/pkg/relay/handlemessage.go b/pkg/relay/handlemessage.go index 4482f091..95b49602 100644 --- a/pkg/relay/handlemessage.go +++ b/pkg/relay/handlemessage.go @@ -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!") diff --git a/pkg/relay/hiddenservice.go b/pkg/relay/hiddenservice.go index 2540b8d3..11faa0cf 100644 --- a/pkg/relay/hiddenservice.go +++ b/pkg/relay/hiddenservice.go @@ -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) } diff --git a/pkg/relay/introductions.go b/pkg/relay/introductions.go index 0f1869d9..8769d087 100644 --- a/pkg/relay/introductions.go +++ b/pkg/relay/introductions.go @@ -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) +} diff --git a/pkg/relay/peel.go b/pkg/relay/peel.go index e1632547..f15f64ee 100644 --- a/pkg/relay/peel.go +++ b/pkg/relay/peel.go @@ -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) { diff --git a/pkg/relay/skins.go b/pkg/relay/skins.go index dc4e9712..a71cbfa5 100644 --- a/pkg/relay/skins.go +++ b/pkg/relay/skins.go @@ -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}) }