diff --git a/cmd/dumpRounds.go b/cmd/dumpRounds.go index f690f5ba1bba6fe54cab9929c6e3f1e88f50f966..88743972921640bc169fd7213122e07254af77b0 100644 --- a/cmd/dumpRounds.go +++ b/cmd/dumpRounds.go @@ -11,16 +11,19 @@ package cmd import ( "encoding/base64" "fmt" - "github.com/spf13/viper" "strconv" "time" + "github.com/spf13/viper" + "github.com/spf13/cobra" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/cmix/rounds" + "gitlab.com/elixxir/client/xxdk" "gitlab.com/xx_network/comms/signature" "gitlab.com/xx_network/crypto/signature/ec" "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/ndf" ) // dumpRoundsCmd allows the user to view network information about a specific @@ -48,91 +51,13 @@ var dumpRoundsCmd = &cobra.Command{ }) waitUntilConnected(connected) - numRequests := len(roundIDs) - requestCh := make(chan bool, numRequests) + roundInfos := dumpRounds(roundIDs, user) - registration := user.GetStorage().GetNDF().Registration - ecp := registration.EllipticPubKey - pubkey, err := ec.LoadPublicKey(ecp) - if err != nil { - jww.FATAL.Panicf("%+v", err) - } - fmt.Printf("registration pubkey: %s\n\n", pubkey.MarshalText()) - - rcb := func(round rounds.Round, success bool) { - if !success { - fmt.Printf("round %v lookup failed", round.ID) - } - - fmt.Printf("Round %v:", round.ID) - fmt.Printf("\n\tBatch size: %v, State: %v", - round.BatchSize, round.State) - fmt.Printf("\n\tUpdateID: %v, AddrSpaceSize: %v", - round.UpdateID, round.AddressSpaceSize) - - fmt.Printf("\n\tTopology: ") - for i, nodeId := range round.Raw.Topology { - nidStr := base64.StdEncoding.EncodeToString( - nodeId) - fmt.Printf("\n\t\t%d\t-\t%s", i, nidStr) - } - - fmt.Printf("\n\tTimestamps: ") - for state, ts := range round.Timestamps { - fmt.Printf("\n\t\t%v \t-\t%v", state, ts) - } - - fmt.Printf("\n\tErrors (%d): ", len(round.Raw.Errors)) - for i, err := range round.Raw.Errors { - fmt.Printf("\n\t\t%d - %v", i, err) - } - - fmt.Printf("\n\tClientErrors (%d): ", - len(round.Raw.ClientErrors)) - for _, ce := range round.Raw.ClientErrors { - fmt.Printf("\n\t\t%s - %v, Src: %v", - base64.StdEncoding.EncodeToString( - ce.ClientId), - ce.Error, - base64.StdEncoding.EncodeToString( - ce.Source)) - } - - ri := round.Raw - err = signature.VerifyEddsa(ri, pubkey) - if err != nil { - fmt.Printf("\n\tECC signature failed: %v", err) - fmt.Printf("\n\tuse trace logging for sig details") - } else { - fmt.Printf("\n\tECC signature succeeded!\n\n") - } - - // fmt.Printf("Round Info RAW: %v\n\n", round) - - // rsapubkey, _ := rsa.LoadPublicKeyFromPem([]byte( - // registration.TlsCertificate)) - // signature.VerifyRsa(ri, rsapubkey) - // if err != nil { - // fmt.Printf("RSA signature failed: %v", err) - // fmt.Printf("use trace logging for sig details") - // } else { - // fmt.Printf("RSA signature succeeded!") - // } - - requestCh <- success - } - - for i := range roundIDs { - rid := roundIDs[i] - err := user.GetCmix().LookupHistoricalRound(rid, rcb) - if err != nil { - fmt.Printf("error on %v: %v", rid, err) - } - } + ndf := user.GetStorage().GetNDF() - for done := 0; done < numRequests; done++ { - res := <-requestCh - fmt.Printf("request complete: %v", res) + for i := range roundInfos { + printRoundInfo(roundInfos[i]) + printAndVerifyRoundSig(roundInfos[i], ndf) } }, } @@ -141,6 +66,101 @@ func init() { rootCmd.AddCommand(dumpRoundsCmd) } +func printRoundInfo(round rounds.Round) { + fmt.Printf("Round %v:", round.ID) + fmt.Printf("\n\tBatch size: %v, State: %v", + round.BatchSize, round.State) + fmt.Printf("\n\tUpdateID: %v, AddrSpaceSize: %v", + round.UpdateID, round.AddressSpaceSize) + + fmt.Printf("\n\tTopology: ") + for i, nodeId := range round.Raw.Topology { + nidStr := base64.StdEncoding.EncodeToString( + nodeId) + fmt.Printf("\n\t\t%d\t-\t%s", i, nidStr) + } + + fmt.Printf("\n\tTimestamps: ") + for state, ts := range round.Timestamps { + fmt.Printf("\n\t\t%v \t-\t%v", state, ts) + } + + fmt.Printf("\n\tErrors (%d): ", len(round.Raw.Errors)) + for i, err := range round.Raw.Errors { + fmt.Printf("\n\t\t%d - %v", i, err) + } + + fmt.Printf("\n\tClientErrors (%d): ", + len(round.Raw.ClientErrors)) + for _, ce := range round.Raw.ClientErrors { + fmt.Printf("\n\t\t%s - %v, Src: %v", + base64.StdEncoding.EncodeToString( + ce.ClientId), + ce.Error, + base64.StdEncoding.EncodeToString( + ce.Source)) + } +} + +func printAndVerifyRoundSig(round rounds.Round, ndf *ndf.NetworkDefinition) { + registration := ndf.Registration + ecp := registration.EllipticPubKey + pubkey, err := ec.LoadPublicKey(ecp) + if err != nil { + jww.FATAL.Panicf("%+v", err) + } + fmt.Printf("registration pubkey: %s\n\n", pubkey.MarshalText()) + + ri := round.Raw + err = signature.VerifyEddsa(ri, pubkey) + if err != nil { + fmt.Printf("\n\tECC signature failed: %v", err) + fmt.Printf("\n\tuse trace logging for sig details") + } else { + fmt.Printf("\n\tECC signature succeeded!\n\n") + } + + // fmt.Printf("Round Info RAW: %v\n\n", round) + + // rsapubkey, _ := rsa.LoadPublicKeyFromPem([]byte( + // registration.TlsCertificate)) + // signature.VerifyRsa(ri, rsapubkey) + // if err != nil { + // fmt.Printf("RSA signature failed: %v", err) + // fmt.Printf("use trace logging for sig details") + // } else { + // fmt.Printf("RSA signature succeeded!") + // } +} + +func dumpRounds(roundIDs []id.Round, user *xxdk.E2e) []rounds.Round { + numRequests := len(roundIDs) + requestCh := make(chan rounds.Round, numRequests) + + rcb := func(round rounds.Round, success bool) { + if !success { + fmt.Printf("round %v lookup failed", round.ID) + } + requestCh <- round + } + + for i := range roundIDs { + rid := roundIDs[i] + err := user.GetCmix().LookupHistoricalRound(rid, rcb) + if err != nil { + fmt.Printf("error on %v: %v", rid, err) + } + } + + roundInfos := make([]rounds.Round, 0) + for done := 0; done < numRequests; done++ { + res := <-requestCh + roundInfos = append(roundInfos, res) + jww.DEBUG.Printf("request complete: %v", res) + } + return roundInfos +} + func parseRoundIDs(roundStrs []string) []id.Round { var roundIDs []id.Round for _, r := range roundStrs { diff --git a/cmd/flags.go b/cmd/flags.go index 5244b7a12b62ee1e1c7d5a718f45b2bbccfad005..8d9c827f2b38cb2ff1668cc9431922fc32eaa99e 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -148,4 +148,9 @@ const ( udSearchEmailFlag = "searchemail" udSearchPhoneFlag = "searchphone" udBatchAddFlag = "batchadd" + + ///////////////// pickup subcommand flags ////////////////////////////// + pickupGW = "gateway" + pickupID = "id" + pickupEphID = "ephid" ) diff --git a/cmd/pickup.go b/cmd/pickup.go new file mode 100644 index 0000000000000000000000000000000000000000..ee4afbe53c923e9ec864e6a5b20536fe91b78a01 --- /dev/null +++ b/cmd/pickup.go @@ -0,0 +1,215 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +// Package cmd initializes the CLI and config parsers as well as the logger. +package cmd + +import ( + "encoding/binary" + "fmt" + "time" + + "github.com/pkg/errors" + + "github.com/spf13/cobra" + jww "github.com/spf13/jwalterweatherman" + "github.com/spf13/viper" + "gitlab.com/elixxir/client/cmix/pickup" + pb "gitlab.com/elixxir/comms/mixmessages" + "gitlab.com/elixxir/primitives/states" + "gitlab.com/xx_network/crypto/csprng" + "gitlab.com/xx_network/crypto/randomness" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" + "gitlab.com/xx_network/primitives/ndf" +) + +// pickupCmd allows the user to view network information about a specific +// round on the network. +var pickupCmd = &cobra.Command{ + Use: "pickup", + Short: "Download the bloomfilter and messages for a round", + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + roundIDs := parseRoundIDs(args) + + cmixParams, e2eParams := initParams() + authCbs := makeAuthCallbacks( + viper.GetBool(unsafeChannelCreationFlag), e2eParams) + user := initE2e(cmixParams, e2eParams, authCbs) + err := user.StartNetworkFollower(5 * time.Second) + if err != nil { + jww.FATAL.Panicf("%+v", err) + } + + connected := make(chan bool, 10) + user.GetCmix().AddHealthCallback( + func(isconnected bool) { + connected <- isconnected + }) + waitUntilConnected(connected) + + ndf := user.GetStorage().GetNDF() + + gwID := getGatewayID(ndf) + clientIDStr := viper.GetString(pickupID) + var clientID *id.ID + if clientIDStr != "" { + clientID = parseRecipient(viper.GetString(pickupID)) + } + eID := viper.GetInt64(pickupEphID) + if eID != 0 { + fmt.Printf("EphID Override: %d\n", eID) + } + + // First we get round info, then we use the timestamps to + // calculate the right ephID and retrieve the right bloom filter + roundInfos := dumpRounds(roundIDs, user) + for i := range roundInfos { + ri := roundInfos[i] + var ephIDs []ephemeral.Id + if clientID != nil { + ephIDs = getEphID(clientID, + uint(ri.AddressSpaceSize), + ri.Timestamps[states.QUEUED]) + } else { + ephIDs = append(ephIDs, int2EphID(eID, + uint(ri.AddressSpaceSize))) + } + + for j := range ephIDs { + ephID := ephIDs[j] + fmt.Printf("Getting messages for %s, %d\n", + ri.ID, ephID.Int64()) + msgRsp, err := getMessagesFromRound(gwID, ri.ID, + ephID, + user.GetComms()) + if err != nil { + fmt.Printf("\n\nround pickup: %+v\n\n", + err) + } + fmt.Printf("=====ROUNDPICKUP=====\n\n%+v\n\n\n", msgRsp) + fmt.Printf("%d messages for user %d", len(msgRsp.Messages), ephIDs) + for k := range msgRsp.Messages { + fmt.Printf("%v\n", msgRsp.Messages[k].PayloadA) + } + } + } + }, +} + +func init() { + pickupCmd.Flags().StringP(pickupGW, "g", "", + "gateway (base64 address string) to download from") + bindFlagHelper(pickupGW, pickupCmd) + + pickupCmd.Flags().StringP(pickupID, "i", "", + "id to check") + bindFlagHelper(pickupID, pickupCmd) + + pickupCmd.Flags().Int64P(pickupEphID, "e", 0, + "ignore id lookup and use this specific eph id (signed int)") + bindFlagHelper(pickupEphID, pickupCmd) + + rootCmd.AddCommand(pickupCmd) +} + +func int2EphID(in int64, addrSize uint) ephemeral.Id { + var out [8]byte + mask := uint64(0xFFFFFFFFFFFFFFFF) >> (64 - addrSize) + + // NOTE: This is just reversing the Int64() function. I have + // no idea why it was done this way... + x := in + if x < 0 { + x = ^x + x = x << 1 + x = x | 1 + } else { + x = x << 1 + } + + shifted := uint64(x) & mask + fmt.Printf("Shifted: %d, %d, %d, %d\n", addrSize, mask, in, shifted) + + binary.BigEndian.PutUint64(out[:], shifted) + return ephemeral.Id(out) +} + +func getEphID(id *id.ID, addrSize uint, + roundStart time.Time) []ephemeral.Id { + + fmt.Printf("Getting EphIDs for %s", roundStart) + + ephIDs, err := ephemeral.GetIdsByRange(id, + addrSize, + roundStart, + time.Duration(12*time.Hour)) + if err != nil { + jww.FATAL.Panicf("%+v", err) + } + + if len(ephIDs) == 0 { + jww.FATAL.Panicf("No ephemeral ids found!") + } + + eIDs := make([]ephemeral.Id, len(ephIDs)) + for i := range ephIDs { + eIDs[i] = ephIDs[i].Id + } + + return eIDs +} +func getGatewayID(ndf *ndf.NetworkDefinition) *id.ID { + gateways := ndf.Gateways + gwID := viper.GetString(pickupGW) + + if gwID == "" { + rng := csprng.NewSystemRNG() + i := randomness.ReadRangeUint32(0, uint32(len(gateways)), rng) + id, err := id.Unmarshal([]byte(gateways[i].ID)) + if err != nil { + jww.FATAL.Panicf("%+v", err) + } + fmt.Printf("selected random gw: %s\n", id) + return id + } + + for i := range gateways { + curID, _ := id.Unmarshal(gateways[i].ID) + jww.DEBUG.Printf("%s ==? %s", gwID, curID) + if curID.String() == gwID { + return curID + } + } + + jww.FATAL.Panicf("%s is not a gateway in the NDF", gwID) + return nil +} + +func getBloomFilter(targetGW string, ephID int64) *pb.ClientBlooms { + return nil +} + +func getMessagesFromRound(targetGW *id.ID, roundID id.Round, + ephID ephemeral.Id, comms pickup.MessageRetrievalComms) ( + *pb.GetMessagesResponse, error) { + + host, ok := comms.GetHost(targetGW) + if !ok { + return nil, errors.Errorf("can't find host %s", targetGW) + } + msgReq := &pb.GetMessages{ + ClientID: ephID[:], + RoundID: uint64(roundID), + Target: targetGW.Marshal(), + } + + jww.DEBUG.Printf("Sending request: %+v", msgReq) + + return comms.RequestMessages(host, msgReq) +} diff --git a/cmix/gateway/hostPool.go b/cmix/gateway/hostPool.go index ca9e0c7b606cb50bd27e932f8da625fcc39aaae9..c90a825fafa8b9b4a9fee9988fe5ab94d1b347dc 100644 --- a/cmix/gateway/hostPool.go +++ b/cmix/gateway/hostPool.go @@ -13,8 +13,13 @@ package gateway import ( - "encoding/binary" "encoding/json" + "math" + "sort" + "strings" + "sync" + "time" + "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/storage" @@ -23,17 +28,12 @@ import ( "gitlab.com/elixxir/crypto/fastRNG" "gitlab.com/elixxir/crypto/shuffle" "gitlab.com/xx_network/comms/connect" + "gitlab.com/xx_network/crypto/randomness" "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/ndf" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/balancer" - "io" - "math" - "sort" - "strings" - "sync" - "time" ) // List of errors that initiate a Host replacement @@ -496,7 +496,8 @@ func (h *HostPool) getAny(length uint32, excluded []*id.ID) []*connect.Host { } // Check the next HostPool index - gwIdx := readRangeUint32(0, h.poolParams.PoolSize, rng) + gwIdx := randomness.ReadRangeUint32(0, h.poolParams.PoolSize, + rng) if _, ok := checked[gwIdx]; !ok { result = append(result, h.hostList[gwIdx]) checked[gwIdx] = nil @@ -537,7 +538,8 @@ func (h *HostPool) getPreferred(targets []*id.ID) []*connect.Host { continue } - gwIdx := readRangeUint32(0, h.poolParams.PoolSize, rng) + gwIdx := randomness.ReadRangeUint32(0, h.poolParams.PoolSize, + rng) if _, ok := checked[gwIdx]; !ok { result[i] = h.hostList[gwIdx] checked[gwIdx] = nil @@ -591,7 +593,8 @@ func (h *HostPool) selectGateway() *id.ID { // Loop until a replacement Host is found for { // Randomly select a new Gw by index in the NDF - ndfIdx := readRangeUint32(0, uint32(len(h.ndf.Gateways)), rng) + ndfIdx := randomness.ReadRangeUint32(0, + uint32(len(h.ndf.Gateways)), rng) // Use the random ndfIdx to obtain a GwId from the NDF gwId, err := id.Unmarshal(h.ndf.Gateways[ndfIdx].ID) @@ -694,7 +697,7 @@ func (h *HostPool) forceAdd(gwId *id.ID) error { } // Randomly select another Gateway in the HostPool for replacement - poolIdx := readRangeUint32(0, h.poolParams.PoolSize, rng) + poolIdx := randomness.ReadRangeUint32(0, h.poolParams.PoolSize, rng) return h.replaceHost(gwId, poolIdx) } @@ -826,31 +829,3 @@ func getPoolSize(ndfLen, maxSize uint32) (uint32, error) { } return poolSize, nil } - -// readUint32 reads an integer from an io.Reader (which should be a CSPRNG). -func readUint32(rng io.Reader) uint32 { - var rndBytes [4]byte - i, err := rng.Read(rndBytes[:]) - if i != 4 || err != nil { - jww.FATAL.Panicf("cannot read from rng: %+v", err) - } - return binary.BigEndian.Uint32(rndBytes[:]) -} - -// readRangeUint32 reduces an integer from 0, MaxUint32 to the range start, end. -func readRangeUint32(start, end uint32, rng io.Reader) uint32 { - size := end - start - // Note that we could just do the part inside the () here, but then extra - // can == size which means a little range is wasted; either choice seems - // negligible, so we went with the "more correct" - extra := (math.MaxUint32%size + 1) % size - limit := math.MaxUint32 - extra - // Loop until we read something inside the limit - for { - res := readUint32(rng) - if res > limit { - continue - } - return (res % size) + start - } -} diff --git a/go.mod b/go.mod index c30177adab05391441f4195854120bf150d307ba..9544ccf3603961767bbd94d0993cdb30e6b98b0e 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( gitlab.com/elixxir/ekv v0.1.7 gitlab.com/elixxir/primitives v0.0.3-0.20220606195757-40f7a589347f gitlab.com/xx_network/comms v0.0.4-0.20220630163702-f3d372ef6acd - gitlab.com/xx_network/crypto v0.0.5-0.20220606200528-3f886fe49e81 + gitlab.com/xx_network/crypto v0.0.5-0.20220729013538-7952978b310b gitlab.com/xx_network/primitives v0.0.4-0.20220712193914-aebd8544396e go.uber.org/ratelimit v0.2.0 golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed diff --git a/go.sum b/go.sum index e8adcd01305845b6db74898bb27221cf3bf81cbf..4d8455a4272c1b161221763923b6ac54b1483324 100644 --- a/go.sum +++ b/go.sum @@ -301,6 +301,10 @@ gitlab.com/xx_network/crypto v0.0.5-0.20220222212031-750f7e8a01f4/go.mod h1:6apv gitlab.com/xx_network/crypto v0.0.5-0.20220317171841-084640957d71/go.mod h1:/SJf+R75E+QepdTLh0H1/udsovxx2Q5ru34q1v0umKk= gitlab.com/xx_network/crypto v0.0.5-0.20220606200528-3f886fe49e81 h1:9HK48ZEGFKLm3HBcE/FdQitllJRYPPS0zeaiRL+MBhI= gitlab.com/xx_network/crypto v0.0.5-0.20220606200528-3f886fe49e81/go.mod h1:/SJf+R75E+QepdTLh0H1/udsovxx2Q5ru34q1v0umKk= +gitlab.com/xx_network/crypto v0.0.5-0.20220729011416-22620d3c99d3 h1:Os4H4+nBkGtDOu00Pferd6uprDWG3kMrM8HWCj71QeI= +gitlab.com/xx_network/crypto v0.0.5-0.20220729011416-22620d3c99d3/go.mod h1:/SJf+R75E+QepdTLh0H1/udsovxx2Q5ru34q1v0umKk= +gitlab.com/xx_network/crypto v0.0.5-0.20220729013538-7952978b310b h1:agxNQUORADob4R6FhucBQDLPWFQ0D2poMyT4SDnPuyo= +gitlab.com/xx_network/crypto v0.0.5-0.20220729013538-7952978b310b/go.mod h1:/SJf+R75E+QepdTLh0H1/udsovxx2Q5ru34q1v0umKk= gitlab.com/xx_network/primitives v0.0.0-20200803231956-9b192c57ea7c/go.mod h1:wtdCMr7DPePz9qwctNoAUzZtbOSHSedcK++3Df3psjA= gitlab.com/xx_network/primitives v0.0.0-20200804183002-f99f7a7284da/go.mod h1:OK9xevzWCaPO7b1wiluVJGk7R5ZsuC7pHY5hteZFQug= gitlab.com/xx_network/primitives v0.0.2/go.mod h1:cs0QlFpdMDI6lAo61lDRH2JZz+3aVkHy+QogOB6F/qc=