diff --git a/pkg/client/client.go b/pkg/client/client.go index 9156ac63..c0f4d6d5 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -88,8 +88,8 @@ func (cl *Client) RegisterConfirmation(hook confirm.Hook, // FindCloaked searches the client identity key and the Sessions for a match. It // returns the session as well, though not all users of this function will need // this. -func (cl *Client) FindCloaked(clk cloak.PubKey) (hdr *prv.Key, pld *prv.Key, - sess *node.Session) { +func (cl *Client) FindCloaked(clk cloak.PubKey) (hdr *prv.Key, + pld *prv.Key, sess *node.Session) { var b cloak.Blinder copy(b[:], clk[:cloak.BlindLen]) @@ -103,7 +103,7 @@ func (cl *Client) FindCloaked(clk cloak.PubKey) (hdr *prv.Key, pld *prv.Key, for i := range cl.Sessions { hash = cloak.Cloak(b, cl.Sessions[i].HeaderBytes) if hash == clk { - log.T.F("found in session %d", i) + log.T.F("found cloaked key in session %d", i) hdr = cl.Sessions[i].HeaderPrv pld = cl.Sessions[i].PayloadPrv sess = cl.Sessions[i] diff --git a/pkg/client/oniontypes_test.go b/pkg/client/oniontypes_test.go index 0c0223f3..72469854 100644 --- a/pkg/client/oniontypes_test.go +++ b/pkg/client/oniontypes_test.go @@ -5,6 +5,7 @@ import ( "time" "github.com/cybriq/qu" + "github.com/indra-labs/indra/pkg/key/prv" "github.com/indra-labs/indra/pkg/node" "github.com/indra-labs/indra/pkg/nonce" "github.com/indra-labs/indra/pkg/sha256" @@ -13,6 +14,7 @@ import ( "github.com/indra-labs/indra/pkg/transport" "github.com/indra-labs/indra/pkg/wire" "github.com/indra-labs/indra/pkg/wire/confirm" + "github.com/indra-labs/indra/pkg/wire/session" ) func TestPing(t *testing.T) { @@ -139,7 +141,47 @@ func TestSendKeys(t *testing.T) { quit.Q() // t.Error("SendKeys got stuck") }() - + // Create a new payment and drop on the payment channel. + sess := session.New() + pmt := sess.ToPayment(1000000) + clients[0].PaymentChan <- pmt + // Send the keys. + var circuit node.Circuit + for i := range circuit { + circuit[i] = clients[0].Sessions[i+1] + } + var hdr, pld [5]*prv.Key + hdr[0], pld[0] = sess.Header, sess.Payload + sk := wire.SendKeys(pmt.ID, hdr, pld, clients[0].Node, + circuit, clients[0].KeySet) + clients[0].RegisterConfirmation(func(cf nonce.ID) { + log.T.S("received payment confirmation ID", cf) + pp := clients[0].PendingPayments.Find(cf) + log.T.F("\nexpected %x\nreceived %x\nfrom\nhdr: %x\npld: %x", + sess.PreimageHash(), + pp.Preimage, + sess.Header.ToBytes(), + sess.Payload.ToBytes(), + ) + if pp.Preimage != sess.PreimageHash() { + t.Errorf("did not find expected preimage: got"+ + " %x expected %x", + pp.Preimage, sess.PreimageHash()) + t.FailNow() + } + _ = pp + // if pp == nil { + // t.Errorf("did not find expected confirmation ID: got"+ + // " %x expected %x", cf, pmt.ID) + // t.FailNow() + // } + log.T.F("SendKeys confirmed %x", cf) + time.Sleep(time.Second) + quit.Q() + }, pmt.ID) + o := sk.Assemble() + b := wire.EncodeOnion(o) + clients[0].Send(clients[0].Nodes[0].AddrPort, b) <-quit.Wait() for _, v := range clients { v.Shutdown() diff --git a/pkg/client/runner.go b/pkg/client/runner.go index f38cbae4..b6649ea7 100644 --- a/pkg/client/runner.go +++ b/pkg/client/runner.go @@ -11,7 +11,6 @@ import ( "github.com/indra-labs/indra/pkg/slice" "github.com/indra-labs/indra/pkg/types" "github.com/indra-labs/indra/pkg/wire" - "github.com/indra-labs/indra/pkg/wire/cipher" "github.com/indra-labs/indra/pkg/wire/confirm" "github.com/indra-labs/indra/pkg/wire/delay" "github.com/indra-labs/indra/pkg/wire/exit" @@ -20,6 +19,7 @@ import ( "github.com/indra-labs/indra/pkg/wire/noop" "github.com/indra-labs/indra/pkg/wire/response" "github.com/indra-labs/indra/pkg/wire/reverse" + "github.com/indra-labs/indra/pkg/wire/session" "github.com/indra-labs/indra/pkg/wire/token" ) @@ -51,9 +51,9 @@ func (cl *Client) runner() (out bool) { break } switch on := onion.(type) { - case *cipher.OnionSkin: + case *session.OnionSkin: recLog(on, b, cl) - cl.cipher(on, b, c) + cl.session(on, b, c) case *confirm.OnionSkin: recLog(on, b, cl) cl.confirm(on, b, c) @@ -84,11 +84,15 @@ func (cl *Client) runner() (out bool) { default: log.I.S("unrecognised packet", b) } + case p := <-cl.PaymentChan: + cl.PendingPayments = cl.PendingPayments.Add(p) } return } -func (cl *Client) cipher(on *cipher.OnionSkin, b slice.Bytes, c *slice.Cursor) { +func (cl *Client) session(on *session.OnionSkin, b slice.Bytes, + c *slice.Cursor) { + // This either is in a forward only SendKeys message or we are the buyer // and these are our session keys. // log.I.S(on) @@ -98,19 +102,25 @@ func (cl *Client) cipher(on *cipher.OnionSkin, b slice.Bytes, c *slice.Cursor) { func (cl *Client) confirm(on *confirm.OnionSkin, b slice.Bytes, c *slice.Cursor) { + // When a confirm arrives check if it is registered for and run // the hook that was registered with it. + log.T.S(cl.Confirms) cl.Confirms.Confirm(on.ID) } -func (cl *Client) delay(on *delay.OnionSkin, b slice.Bytes, cur *slice.Cursor) { +func (cl *Client) delay(on *delay.OnionSkin, b slice.Bytes, + cur *slice.Cursor) { + // 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. } -func (cl *Client) exit(on *exit.OnionSkin, b slice.Bytes, c *slice.Cursor) { +func (cl *Client) exit(on *exit.OnionSkin, b slice.Bytes, + c *slice.Cursor) { + // payload is forwarded to a local port and the result is forwarded // back with a reverse header. var e error @@ -156,7 +166,9 @@ func (cl *Client) forward(on *forward.OnionSkin, b slice.Bytes, } } -func (cl *Client) layer(on *layer.OnionSkin, b slice.Bytes, c *slice.Cursor) { +func (cl *Client) layer(on *layer.OnionSkin, b slice.Bytes, + c *slice.Cursor) { + // this is probably an encrypted layer for us. hdr, _, _ := cl.FindCloaked(on.Cloak) if hdr == nil { @@ -168,7 +180,9 @@ func (cl *Client) layer(on *layer.OnionSkin, b slice.Bytes, c *slice.Cursor) { cl.Node.Send(b) } -func (cl *Client) noop(on *noop.OnionSkin, b slice.Bytes, c *slice.Cursor) { +func (cl *Client) noop(on *noop.OnionSkin, b slice.Bytes, + c *slice.Cursor) { + // this won't happen normally } @@ -214,6 +228,8 @@ func (cl *Client) reverse(on *reverse.OnionSkin, b slice.Bytes, } cl.Node.Send(b[start:]) default: + // If a reverse is not followed by an onion layer the + // message is incorrectly formed, just drop it. return } } else { @@ -223,12 +239,16 @@ func (cl *Client) reverse(on *reverse.OnionSkin, b slice.Bytes, } -func (cl *Client) response(on *response.OnionSkin, b slice.Bytes, cur *slice.Cursor) { +func (cl *Client) response(on *response.OnionSkin, b slice.Bytes, + cur *slice.Cursor) { + // Response is a payload from an exit message. cl.ExitHooks.Find(on.Hash, on.Bytes) } -func (cl *Client) token(t *token.OnionSkin, b slice.Bytes, cur *slice.Cursor) { +func (cl *Client) token(t *token.OnionSkin, b slice.Bytes, + cur *slice.Cursor) { + // not really sure if we are using these. return } diff --git a/pkg/client/utils_test.go b/pkg/client/utils_test.go index 90d47680..ef77fa85 100644 --- a/pkg/client/utils_test.go +++ b/pkg/client/utils_test.go @@ -38,8 +38,7 @@ func CreateMockCircuitClients() (clients []*Client, e error) { clients[i].Node = nodes[i] // create a session for all but the first if i > 0 { - sessions[i-1] = node.NewSession(nonce.NewID(), - nodes[i], math.MaxUint64) + sessions[i-1] = node.NewSession(nonce.NewID(), nodes[i], math.MaxUint64, nil, nil) // Add session to node, so it will be able to relay if // it gets a message with the key. nodes[i].Sessions = append(nodes[i].Sessions, diff --git a/pkg/node/node.go b/pkg/node/node.go index 6f0da0e5..bbf85304 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -62,7 +62,7 @@ func New(addr *netip.AddrPort, idPub *pub.Key, idPrv *prv.Key, IdentityPrv: idPrv, PaymentChan: make(PaymentChan), } - n.Sessions = append(n.Sessions, NewSession(id, n, 0)) + n.Sessions = append(n.Sessions, NewSession(id, n, 0, nil, nil)) return } diff --git a/pkg/node/payment.go b/pkg/node/payment.go index d972502f..848d44c0 100644 --- a/pkg/node/payment.go +++ b/pkg/node/payment.go @@ -2,10 +2,12 @@ package node import ( "github.com/indra-labs/indra/pkg/lnwire" + "github.com/indra-labs/indra/pkg/nonce" "github.com/indra-labs/indra/pkg/sha256" ) type Payment struct { + nonce.ID Preimage sha256.Hash Amount lnwire.MilliSatoshi } @@ -32,9 +34,9 @@ func (p PendingPayments) Delete(preimage sha256.Hash) (pp PendingPayments) { return } -func (p PendingPayments) Find(preimage sha256.Hash) (pp *Payment) { +func (p PendingPayments) Find(id nonce.ID) (pp *Payment) { for i := range p { - if p[i].Preimage == preimage { + if p[i].ID == id { return p[i] } } diff --git a/pkg/node/session.go b/pkg/node/session.go index ea878d77..13bf0c8a 100644 --- a/pkg/node/session.go +++ b/pkg/node/session.go @@ -22,15 +22,17 @@ type Session struct { // // Purchasing a session the seller returns a token, based on a requested data // allocation. -func NewSession(id nonce.ID, node *Node, rem uint64) (s *Session) { +func NewSession(id nonce.ID, node *Node, rem uint64, + hdrPrv *prv.Key, pldPrv *prv.Key) (s *Session) { var e error - var hdrPrv, pldPrv *prv.Key - if hdrPrv, e = prv.GenerateKey(); check(e) { + if hdrPrv == nil || pldPrv == nil { + if hdrPrv, e = prv.GenerateKey(); check(e) { + } + if pldPrv, e = prv.GenerateKey(); check(e) { + } } hdrPub := pub.Derive(hdrPrv) - if pldPrv, e = prv.GenerateKey(); check(e) { - } pldPub := pub.Derive(pldPrv) s = &Session{ ID: id, diff --git a/pkg/wire/cipher/cipher.go b/pkg/wire/cipher/cipher.go deleted file mode 100644 index c4abdcbf..00000000 --- a/pkg/wire/cipher/cipher.go +++ /dev/null @@ -1,55 +0,0 @@ -package cipher - -import ( - "github.com/indra-labs/indra" - "github.com/indra-labs/indra/pkg/key/prv" - "github.com/indra-labs/indra/pkg/key/pub" - log2 "github.com/indra-labs/indra/pkg/log" - "github.com/indra-labs/indra/pkg/slice" - "github.com/indra-labs/indra/pkg/types" - "github.com/indra-labs/indra/pkg/wire/magicbytes" -) - -const ( - MagicString = "cf" - Len = magicbytes.Len + pub.KeyLen*2 -) - -var ( - log = log2.GetLogger(indra.PathBase) - check = log.E.Chk - Magic = slice.Bytes(MagicString) - _ types.Onion = &OnionSkin{} -) - -// OnionSkin cipher delivers a pair of private keys to be used in association -// with a reply.Type specifically in the situation of a node bootstrapping -// sessions. -// -// After ~10 seconds these can be purged from the cache as they are otherwise a -// DoS vector buffer flooding. -type OnionSkin struct { - Header, Payload *prv.Key - types.Onion -} - -func (x *OnionSkin) Inner() types.Onion { return x.Onion } -func (x *OnionSkin) Insert(o types.Onion) { x.Onion = o } -func (x *OnionSkin) Len() int { return Len + x.Onion.Len() } -func (x *OnionSkin) Encode(b slice.Bytes, c *slice.Cursor) { - copy(b[*c:c.Inc(magicbytes.Len)], Magic) - hdr := x.Header.ToBytes() - pld := x.Payload.ToBytes() - copy(b[*c:c.Inc(prv.KeyLen)], hdr[:]) - copy(b[*c:c.Inc(prv.KeyLen)], pld[:]) - x.Onion.Encode(b, c) -} -func (x *OnionSkin) 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)) - } - x.Header = prv.PrivkeyFromBytes(b[*c:c.Inc(prv.KeyLen)]) - x.Payload = prv.PrivkeyFromBytes(b[*c:c.Inc(prv.KeyLen)]) - return -} diff --git a/pkg/wire/codec.go b/pkg/wire/codec.go index c5102758..c3be6bd2 100644 --- a/pkg/wire/codec.go +++ b/pkg/wire/codec.go @@ -8,7 +8,6 @@ import ( log2 "github.com/indra-labs/indra/pkg/log" "github.com/indra-labs/indra/pkg/slice" "github.com/indra-labs/indra/pkg/types" - "github.com/indra-labs/indra/pkg/wire/cipher" "github.com/indra-labs/indra/pkg/wire/confirm" "github.com/indra-labs/indra/pkg/wire/delay" "github.com/indra-labs/indra/pkg/wire/exit" @@ -17,6 +16,7 @@ import ( "github.com/indra-labs/indra/pkg/wire/magicbytes" "github.com/indra-labs/indra/pkg/wire/response" "github.com/indra-labs/indra/pkg/wire/reverse" + "github.com/indra-labs/indra/pkg/wire/session" "github.com/indra-labs/indra/pkg/wire/token" ) @@ -35,8 +35,8 @@ func EncodeOnion(on types.Onion) (b slice.Bytes) { func PeelOnion(b slice.Bytes, c *slice.Cursor) (on types.Onion, e error) { switch b[*c:c.Inc(magicbytes.Len)].String() { - case cipher.MagicString: - o := &cipher.OnionSkin{} + case session.MagicString: + o := &session.OnionSkin{} if e = o.Decode(b, c); check(e) { return } diff --git a/pkg/wire/codec_test.go b/pkg/wire/codec_test.go index 70dca5b7..d59851af 100644 --- a/pkg/wire/codec_test.go +++ b/pkg/wire/codec_test.go @@ -15,7 +15,6 @@ import ( "github.com/indra-labs/indra/pkg/slice" "github.com/indra-labs/indra/pkg/tests" "github.com/indra-labs/indra/pkg/types" - "github.com/indra-labs/indra/pkg/wire/cipher" "github.com/indra-labs/indra/pkg/wire/confirm" "github.com/indra-labs/indra/pkg/wire/delay" "github.com/indra-labs/indra/pkg/wire/exit" @@ -23,6 +22,7 @@ import ( "github.com/indra-labs/indra/pkg/wire/layer" "github.com/indra-labs/indra/pkg/wire/response" "github.com/indra-labs/indra/pkg/wire/reverse" + "github.com/indra-labs/indra/pkg/wire/session" "github.com/indra-labs/indra/pkg/wire/token" ) @@ -40,9 +40,9 @@ func TestOnionSkins_Cipher(t *testing.T) { if onc, e = PeelOnion(onb, c); check(e) { t.FailNow() } - var ci *cipher.OnionSkin + var ci *session.OnionSkin var ok bool - if ci, ok = onc.(*cipher.OnionSkin); !ok { + if ci, ok = onc.(*session.OnionSkin); !ok { t.Error("did not unwrap expected type") t.FailNow() } diff --git a/pkg/wire/layers.go b/pkg/wire/layers.go index 6b362519..c1d38f58 100644 --- a/pkg/wire/layers.go +++ b/pkg/wire/layers.go @@ -10,7 +10,6 @@ import ( "github.com/indra-labs/indra/pkg/sha256" "github.com/indra-labs/indra/pkg/slice" "github.com/indra-labs/indra/pkg/types" - "github.com/indra-labs/indra/pkg/wire/cipher" "github.com/indra-labs/indra/pkg/wire/confirm" "github.com/indra-labs/indra/pkg/wire/delay" "github.com/indra-labs/indra/pkg/wire/exit" @@ -19,6 +18,7 @@ import ( "github.com/indra-labs/indra/pkg/wire/noop" "github.com/indra-labs/indra/pkg/wire/response" "github.com/indra-labs/indra/pkg/wire/reverse" + "github.com/indra-labs/indra/pkg/wire/session" "github.com/indra-labs/indra/pkg/wire/token" ) @@ -32,7 +32,7 @@ func (o OnionSkins) Cipher(hdr, pld *prv.Key) OnionSkins { if hdr == nil || pld == nil { return o } - return append(o, &cipher.OnionSkin{ + return append(o, &session.OnionSkin{ Header: hdr, Payload: pld, Onion: &noop.OnionSkin{}, diff --git a/pkg/wire/onion.go b/pkg/wire/onion.go index 1dc69bf9..b8f36d67 100644 --- a/pkg/wire/onion.go +++ b/pkg/wire/onion.go @@ -92,8 +92,8 @@ func SendExit(payload slice.Bytes, port uint16, client *node.Node, // // This message's last layer is a Confirmation, which allows the client to know // that the keys were successfully delivered. -func SendKeys(id nonce.ID, hdr, pld []*prv.Key, - client *node.Node, hop []*node.Node, set *signer.KeySet) OnionSkins { +func SendKeys(id nonce.ID, hdr, pld [5]*prv.Key, + client *node.Node, hop node.Circuit, set *signer.KeySet) OnionSkins { n := GenNonces(6) return OnionSkins{}. diff --git a/pkg/wire/session/session.go b/pkg/wire/session/session.go new file mode 100644 index 00000000..1d9822e8 --- /dev/null +++ b/pkg/wire/session/session.go @@ -0,0 +1,105 @@ +package session + +import ( + "github.com/indra-labs/indra" + "github.com/indra-labs/indra/pkg/key/prv" + "github.com/indra-labs/indra/pkg/lnwire" + log2 "github.com/indra-labs/indra/pkg/log" + "github.com/indra-labs/indra/pkg/node" + "github.com/indra-labs/indra/pkg/nonce" + "github.com/indra-labs/indra/pkg/sha256" + "github.com/indra-labs/indra/pkg/slice" + "github.com/indra-labs/indra/pkg/types" + "github.com/indra-labs/indra/pkg/wire/magicbytes" + "github.com/indra-labs/indra/pkg/wire/noop" +) + +const ( + MagicString = "ss" + Len = magicbytes.Len + prv.KeyLen*2 +) + +var ( + log = log2.GetLogger(indra.PathBase) + check = log.E.Chk + Magic = slice.Bytes(MagicString) + _ types.Onion = &OnionSkin{} +) + +// OnionSkin session delivers a pair of private keys to a relay that represent +// the preimage referred to in a Lightning payment for a session. +// +// The preimage is hashed by the buyer to use in the payment, and when the relay +// receives it, it can then hash the two private keys to match it with the +// payment preimage hash, which proves the buyer paid, and simultaneously +// provides the session keys that both forward and reverse messages will use, +// each in different ways. +// +// Exit nodes are provided the ciphers to encrypt for the three hops back to the +// client, but they do not have either public or private part of the Header or +// Payload keys of the return hops, which are used to conceal their respective +// message sections. +// +// Thus, they cannot decrypt the header, but they can encrypt the payload with +// the three layers of encryption that the reverse path hops have the private +// keys to decrypt. By this, the path in the header is concealed and the payload +// is concealed to the hops except for the encryption layer they decrypt using +// their Payload key, delivered in this message. +type OnionSkin struct { + Header, Payload *prv.Key + types.Onion +} + +func New() (x *OnionSkin) { + var e error + var hdrPrv, pldPrv *prv.Key + if hdrPrv, e = prv.GenerateKey(); check(e) { + return + } + if pldPrv, e = prv.GenerateKey(); check(e) { + return + } + + return &OnionSkin{ + Header: hdrPrv, + Payload: pldPrv, + Onion: &noop.OnionSkin{}, + } +} + +func (x *OnionSkin) PreimageHash() sha256.Hash { + h, p := x.Header.ToBytes(), x.Payload.ToBytes() + return sha256.Single(append(h[:], p[:]...)) +} + +func (x *OnionSkin) ToPayment(amount lnwire. + MilliSatoshi) (p *node.Payment) { + + p = &node.Payment{ + ID: nonce.NewID(), + Preimage: x.PreimageHash(), + Amount: amount, + } + return +} + +func (x *OnionSkin) Inner() types.Onion { return x.Onion } +func (x *OnionSkin) Insert(o types.Onion) { x.Onion = o } +func (x *OnionSkin) Len() int { return Len + x.Onion.Len() } +func (x *OnionSkin) Encode(b slice.Bytes, c *slice.Cursor) { + copy(b[*c:c.Inc(magicbytes.Len)], Magic) + hdr := x.Header.ToBytes() + pld := x.Payload.ToBytes() + copy(b[*c:c.Inc(prv.KeyLen)], hdr[:]) + copy(b[*c:c.Inc(prv.KeyLen)], pld[:]) + x.Onion.Encode(b, c) +} +func (x *OnionSkin) 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)) + } + x.Header = prv.PrivkeyFromBytes(b[*c:c.Inc(prv.KeyLen)]) + x.Payload = prv.PrivkeyFromBytes(b[*c:c.Inc(prv.KeyLen)]) + return +} diff --git a/version.go b/version.go index 4cc1156f..a94d6bfd 100644 --- a/version.go +++ b/version.go @@ -10,9 +10,9 @@ var ( // GitRef is the gitref, as in refs/heads/branchname. GitRef = "refs/heads/protocol" // ParentGitCommit is the commit hash of the parent HEAD. - ParentGitCommit = "6f271556c60e5ff2a5915d48a1b4f9ed7559dff4" + ParentGitCommit = "33257572125b76ba2bcad68dd54c92e2a4f44158" // BuildTime stores the time when the current binary was built. - BuildTime = "2023-01-14T08:54:21Z" + BuildTime = "2023-01-14T16:05:17Z" // SemVer lists the (latest) git tag on the release. SemVer = "v0.1.7" // PathBase is the path base returned from runtime caller.