diff --git a/api/authenticatedChannel.go b/api/authenticatedChannel.go index 0af89ff00d7f87f5590d55f4b2dfcd2788d013d0..88682af983c107345b6415648bec9558f2eb4d6b 100644 --- a/api/authenticatedChannel.go +++ b/api/authenticatedChannel.go @@ -20,16 +20,17 @@ import ( // RequestAuthenticatedChannel sends a request to another party to establish an // authenticated channel // It will not run if the network status is not healthy -// An error will be returned if a channel already exists, if a request was -// already received, or if a request was already sent +// An error will be returned if a channel already exists or if a request was +// already received // When a confirmation occurs, the channel will be created and the callback // will be called +// Can be retried. func (c *Client) RequestAuthenticatedChannel(recipient, me contact.Contact, - message string) error { + message string) (id.Round, error) { jww.INFO.Printf("RequestAuthenticatedChannel(%s)", recipient.ID) if !c.network.GetHealthTracker().IsHealthy() { - return errors.New("Cannot request authenticated channel " + + return 0, errors.New("Cannot request authenticated channel " + "creation when the network is not healthy") } @@ -60,11 +61,12 @@ func (c *Client) GetAuthenticatedChannelRequest(partner *id.ID) (contact.Contact // An error will be returned if a channel already exists, if a request doest // exist, or if the passed in contact does not exactly match the received // request -func (c *Client) ConfirmAuthenticatedChannel(recipient contact.Contact) error { +// Can be retried. +func (c *Client) ConfirmAuthenticatedChannel(recipient contact.Contact) (id.Round, error) { jww.INFO.Printf("ConfirmAuthenticatedChannel(%s)", recipient.ID) if !c.network.GetHealthTracker().IsHealthy() { - return errors.New("Cannot request authenticated channel " + + return 0, errors.New("Cannot request authenticated channel " + "creation when the network is not healthy") } diff --git a/api/client.go b/api/client.go index e70241ec757547aa7c3c81291cad52dd70aec947..ae87fdc6dc36a974875f5a73ddea87ed52fbded1 100644 --- a/api/client.go +++ b/api/client.go @@ -84,33 +84,12 @@ func NewClient(ndfJSON, storageDir string, password []byte, registrationCode str protoUser := createNewUser(rngStream, cmixGrp, e2eGrp) - // Get current client version - currentVersion, err := version.ParseVersion(SEMVER) - if err != nil { - return errors.WithMessage(err, "Could not parse version string.") - } - - // Create Storage - passwordStr := string(password) - storageSess, err := storage.New(storageDir, passwordStr, protoUser, - currentVersion, cmixGrp, e2eGrp, rngStreamGen) + err = checkVersionAndSetupStorage(def, storageDir, password, protoUser, + cmixGrp, e2eGrp, rngStreamGen, false, registrationCode) if err != nil { return err } - // Save NDF to be used in the future - storageSess.SetBaseNDF(def) - - //store the registration code for later use - storageSess.SetRegCode(registrationCode) - - //move the registration state to keys generated - err = storageSess.ForwardRegistrationStatus(storage.KeyGenComplete) - if err != nil { - return errors.WithMessage(err, "Failed to denote state "+ - "change in session") - } - //TODO: close the session return nil } @@ -135,29 +114,39 @@ func NewPrecannedClient(precannedID uint, defJSON, storageDir string, password [ protoUser := createPrecannedUser(precannedID, rngStream, cmixGrp, e2eGrp) - // Get current client version - currentVersion, err := version.ParseVersion(SEMVER) + err = checkVersionAndSetupStorage(def, storageDir, password, protoUser, + cmixGrp, e2eGrp, rngStreamGen, true, "") if err != nil { - return errors.WithMessage(err, "Could not parse version string.") + return err } + //TODO: close the session + return nil +} - // Create Storage - passwordStr := string(password) - storageSess, err := storage.New(storageDir, passwordStr, protoUser, - currentVersion, cmixGrp, e2eGrp, rngStreamGen) +// NewVanityClient creates a user with a receptionID that starts with the supplied prefix +// It creates client storage, generates keys, connects, and registers +// with the network. Note that this does not register a username/identity, but +// merely creates a new cryptographic identity for adding such information +// at a later date. +func NewVanityClient(ndfJSON, storageDir string, password []byte, registrationCode string, userIdPrefix string) error { + jww.INFO.Printf("NewVanityClient()") + // Use fastRNG for RNG ops (AES fortuna based RNG using system RNG) + rngStreamGen := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG) + rngStream := rngStreamGen.GetStream() + + // Parse the NDF + def, err := parseNDF(ndfJSON) if err != nil { return err } + cmixGrp, e2eGrp := decodeGroups(def) - // Save NDF to be used in the future - storageSess.SetBaseNDF(def) + protoUser := createNewVanityUser(rngStream, cmixGrp, e2eGrp, userIdPrefix) - //move the registration state to indicate registered with permissioning - err = storageSess.ForwardRegistrationStatus( - storage.PermissioningComplete) + err = checkVersionAndSetupStorage(def, storageDir, password, protoUser, + cmixGrp, e2eGrp, rngStreamGen, false, registrationCode) if err != nil { - return errors.WithMessage(err, "Failed to denote state "+ - "change in session") + return err } //TODO: close the session @@ -583,3 +572,43 @@ func decodeGroups(ndf *ndf.NetworkDefinition) (cmixGrp, e2eGrp *cyclic.Group) { return cmixGrp, e2eGrp } + +// checkVersionAndSetupStorage is common code shared by NewClient, NewPrecannedClient and NewVanityClient +// it checks client version and creates a new storage for user data +func checkVersionAndSetupStorage(def *ndf.NetworkDefinition, storageDir string, password []byte, + protoUser user.User, cmixGrp, e2eGrp *cyclic.Group, rngStreamGen *fastRNG.StreamGenerator, + isPrecanned bool, registrationCode string) error { + // Get current client version + currentVersion, err := version.ParseVersion(SEMVER) + if err != nil { + return errors.WithMessage(err, "Could not parse version string.") + } + + // Create Storage + passwordStr := string(password) + storageSess, err := storage.New(storageDir, passwordStr, protoUser, + currentVersion, cmixGrp, e2eGrp, rngStreamGen) + if err != nil { + return err + } + + // Save NDF to be used in the future + storageSess.SetBaseNDF(def) + + if !isPrecanned { + //store the registration code for later use + storageSess.SetRegCode(registrationCode) + //move the registration state to keys generated + err = storageSess.ForwardRegistrationStatus(storage.KeyGenComplete) + } else { + //move the registration state to indicate registered with permissioning + err = storageSess.ForwardRegistrationStatus(storage.PermissioningComplete) + } + + if err != nil { + return errors.WithMessage(err, "Failed to denote state "+ + "change in session") + } + + return nil +} diff --git a/api/user.go b/api/user.go index 892d84c63c8fb0e29001774c6c645f351b3d00b8..48d7f992dfa32b48a3905752c55459ddc710e701 100644 --- a/api/user.go +++ b/api/user.go @@ -17,6 +17,10 @@ import ( "gitlab.com/xx_network/crypto/xx" "gitlab.com/xx_network/primitives/id" "math/rand" + "regexp" + "runtime" + "strings" + "sync" ) const ( @@ -134,3 +138,126 @@ func createPrecannedUser(precannedID uint, rng csprng.Source, cmix, e2e *cyclic. ReceptionRSA: rsaKey, } } + +// createNewVanityUser generates an identity for cMix +// The identity's ReceptionID is not random but starts with the supplied prefix +func createNewVanityUser(rng csprng.Source, cmix, e2e *cyclic.Group, prefix string) user.User { + // CMIX Keygen + // FIXME: Why 256 bits? -- this is spec but not explained, it has + // to do with optimizing operations on one side and still preserves + // decent security -- cite this. + cMixKeyBytes, err := csprng.GenerateInGroup(cmix.GetPBytes(), 256, rng) + if err != nil { + jww.FATAL.Panicf(err.Error()) + } + + // DH Keygen + // FIXME: Why 256 bits? -- this is spec but not explained, it has + // to do with optimizing operations on one side and still preserves + // decent security -- cite this. Why valid for BOTH e2e and cmix? + e2eKeyBytes, err := csprng.GenerateInGroup(e2e.GetPBytes(), 256, rng) + if err != nil { + jww.FATAL.Panicf(err.Error()) + } + + // RSA Keygen (4096 bit defaults) + transmissionRsaKey, err := rsa.GenerateKey(rng, rsa.DefaultRSABitLen) + if err != nil { + jww.FATAL.Panicf(err.Error()) + } + + // Salt, UID, etc gen + transmissionSalt := make([]byte, SaltSize) + n, err := csprng.NewSystemRNG().Read(transmissionSalt) + if err != nil { + jww.FATAL.Panicf(err.Error()) + } + if n != SaltSize { + jww.FATAL.Panicf("transmissionSalt size too small: %d", n) + } + transmissionID, err := xx.NewID(transmissionRsaKey.GetPublic(), transmissionSalt, id.User) + if err != nil { + jww.FATAL.Panicf(err.Error()) + } + + receptionRsaKey, err := rsa.GenerateKey(rng, rsa.DefaultRSABitLen) + if err != nil { + jww.FATAL.Panicf(err.Error()) + } + + var mu sync.Mutex // just in case more than one go routine tries to access receptionSalt and receptionID + done := make(chan struct{}) + found:= make(chan bool) + wg:= &sync.WaitGroup{} + cores := runtime.NumCPU() + + var receptionSalt []byte + var receptionID *id.ID + + pref := prefix + ignoreCase := false + // check if case-insensitivity is enabled + if strings.HasPrefix(prefix, "(?i)") { + pref = strings.ToLower(pref[4:]) + ignoreCase = true + } + // Check if prefix contains valid Base64 characters + match, _ := regexp.MatchString("^[A-Za-z0-9+/]+$", pref) + if match == false { + jww.FATAL.Panicf("Prefix contains non-Base64 characters") + } + jww.INFO.Printf("Vanity userID generation started. Prefix: %s Ignore-Case: %v NumCPU: %d", pref, ignoreCase, cores) + for w := 0; w < cores; w++{ + wg.Add(1) + go func() { + rSalt := make([]byte, SaltSize) + for { + select { + case <- done: + defer wg.Done() + return + default: + n, err = csprng.NewSystemRNG().Read(rSalt) + if err != nil { + jww.FATAL.Panicf(err.Error()) + } + if n != SaltSize { + jww.FATAL.Panicf("receptionSalt size too small: %d", n) + } + rID, err := xx.NewID(receptionRsaKey.GetPublic(), rSalt, id.User) + if err != nil { + jww.FATAL.Panicf(err.Error()) + } + id := rID.String() + if ignoreCase { + id = strings.ToLower(id) + } + if strings.HasPrefix(id, pref) { + mu.Lock() + receptionID = rID + receptionSalt = rSalt + mu.Unlock() + found <- true + defer wg.Done() + return + } + } + } + }() + } + // wait for a solution then close the done channel to signal the workers to exit + <- found + close(done) + wg.Wait() + return user.User{ + TransmissionID: transmissionID.DeepCopy(), + TransmissionSalt: transmissionSalt, + TransmissionRSA: transmissionRsaKey, + ReceptionID: receptionID.DeepCopy(), + ReceptionSalt: receptionSalt, + ReceptionRSA: receptionRsaKey, + Precanned: false, + CmixDhPrivateKey: cmix.NewIntFromBytes(cMixKeyBytes), + E2eDhPrivateKey: e2e.NewIntFromBytes(e2eKeyBytes), + } +} diff --git a/auth/confirm.go b/auth/confirm.go index fee1433a2c22b7323cb5c1ce9d1ea57c6108c9a4..d91f81e33e9e1d52aac5136e620ec405842ccd24 100644 --- a/auth/confirm.go +++ b/auth/confirm.go @@ -12,26 +12,23 @@ import ( jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/interfaces/params" - "gitlab.com/elixxir/client/interfaces/utility" "gitlab.com/elixxir/client/storage" - ds "gitlab.com/elixxir/comms/network/dataStructures" + "gitlab.com/xx_network/primitives/id" "gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/crypto/diffieHellman" cAuth "gitlab.com/elixxir/crypto/e2e/auth" "gitlab.com/elixxir/primitives/format" - "gitlab.com/elixxir/primitives/states" "io" - "time" ) func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, - storage *storage.Session, net interfaces.NetworkManager) error { + storage *storage.Session, net interfaces.NetworkManager) (id.Round, error) { /*edge checking*/ // check that messages can be sent over the network if !net.GetHealthTracker().IsHealthy() { - return errors.New("Cannot confirm authenticated message " + + return 0, errors.New("Cannot confirm authenticated message " + "when the network is not healthy") } @@ -40,14 +37,14 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, // the lock storedContact, err := storage.Auth().GetReceivedRequest(partner.ID) if err != nil { - return errors.Errorf("failed to find a pending Auth Request: %s", + return 0, errors.Errorf("failed to find a pending Auth Request: %s", err) } // verify the passed contact matches what is stored if storedContact.DhPubKey.Cmp(partner.DhPubKey) != 0 { storage.Auth().Fail(partner.ID) - return errors.WithMessage(err, "Pending Auth Request has different "+ + return 0, errors.WithMessage(err, "Pending Auth Request has different "+ "pubkey than stored") } @@ -68,7 +65,7 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, _, err = rng.Read(salt) if err != nil { storage.Auth().Fail(partner.ID) - return errors.Wrap(err, "Failed to generate salt for "+ + return 0, errors.Wrap(err, "Failed to generate salt for "+ "confirmation") } @@ -108,21 +105,19 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, if err := storage.E2e().AddPartner(partner.ID, partner.DhPubKey, newPrivKey, p, p); err != nil { storage.Auth().Fail(partner.ID) - return errors.Errorf("Failed to create channel with partner (%s) "+ + return 0, errors.Errorf("Failed to create channel with partner (%s) "+ "on confirmation: %+v", partner.ID, err) } // delete the in progress negotiation // this unlocks the request lock - if err := storage.Auth().Delete(partner.ID); err != nil { - return errors.Errorf("UNRECOVERABLE! Failed to delete in "+ + //fixme - do these deletes at a later date + /*if err := storage.Auth().Delete(partner.ID); err != nil { + return 0, errors.Errorf("UNRECOVERABLE! Failed to delete in "+ "progress negotiation with partner (%s) after creating confirmation: %+v", partner.ID, err) - } - - //store the message as a critical message so it will always be sent - storage.GetCriticalRawMessages().AddProcessing(cmixMsg, partner.ID) + }*/ jww.INFO.Printf("Confirming Auth with %s, msgDigest: %s", partner.ID, cmixMsg.Digest()) @@ -134,39 +129,11 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, // retried jww.INFO.Printf("Auth Confirm with %s (msgDigest: %s) failed "+ "to transmit: %+v", partner.ID, cmixMsg.Digest(), err) - storage.GetCriticalRawMessages().Failed(cmixMsg, partner.ID) - return errors.WithMessage(err, "Auth Confirm Failed to transmit") + return 0, errors.WithMessage(err, "Auth Confirm Failed to transmit") } jww.INFO.Printf("Confirm Request with %s (msgDigest: %s) sent on round %d", partner.ID, cmixMsg.Digest(), round) - /*check message delivery*/ - sendResults := make(chan ds.EventReturn, 1) - roundEvents := net.GetInstance().GetRoundEvents() - - roundEvents.AddRoundEventChan(round, sendResults, 1*time.Minute, - states.COMPLETED, states.FAILED) - - success, numFailed, _ := utility.TrackResults(sendResults, 1) - if !success { - if numFailed > 0 { - jww.INFO.Printf("Auth Confirm with %s (msgDigest: %s) failed "+ - "delivery due to round failure, will retry on reconnect", - partner.ID, cmixMsg.Digest()) - } else { - jww.INFO.Printf("Auth Confirm with %s (msgDigest: %s) failed "+ - "delivery due to timeout, will retry on reconnect", - partner.ID, cmixMsg.Digest()) - } - jww.ERROR.Printf("auth confirm failed to transmit, will be " + - "handled on reconnect") - storage.GetCriticalRawMessages().Failed(cmixMsg, partner.ID) - } else { - jww.INFO.Printf("Auth Confirm with %s (msgDigest: %s) delivered "+ - "sucesfully", partner.ID, cmixMsg.Digest()) - storage.GetCriticalRawMessages().Succeeded(cmixMsg, partner.ID) - } - - return nil + return round, nil } diff --git a/auth/request.go b/auth/request.go index 7c963123b49dd532734871c687277a318887093d..c43707904f835b19d1e64ba53cb4ec9fe550355f 100644 --- a/auth/request.go +++ b/auth/request.go @@ -12,59 +12,59 @@ import ( jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/interfaces/params" - "gitlab.com/elixxir/client/interfaces/utility" "gitlab.com/elixxir/client/storage" "gitlab.com/elixxir/client/storage/auth" "gitlab.com/elixxir/client/storage/e2e" - ds "gitlab.com/elixxir/comms/network/dataStructures" "gitlab.com/elixxir/crypto/contact" + "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/diffieHellman" cAuth "gitlab.com/elixxir/crypto/e2e/auth" "gitlab.com/elixxir/primitives/format" - "gitlab.com/elixxir/primitives/states" + "gitlab.com/xx_network/primitives/id" "io" "strings" - "time" ) const terminator = ";" func RequestAuth(partner, me contact.Contact, message string, rng io.Reader, - storage *storage.Session, net interfaces.NetworkManager) error { + storage *storage.Session, net interfaces.NetworkManager) (id.Round, error) { /*edge checks generation*/ // check that an authenticated channel does not already exists if _, err := storage.E2e().GetPartner(partner.ID); err == nil || !strings.Contains(err.Error(), e2e.NoPartnerErrorStr) { - return errors.Errorf("Authenticated channel already " + + return 0, errors.Errorf("Authenticated channel already " + "established with partner") } // check that the request is being sent from the proper ID if !me.ID.Cmp(storage.GetUser().ReceptionID) { - return errors.Errorf("Authenticated channel request " + + return 0, errors.Errorf("Authenticated channel request " + "can only be sent from user's identity") } // check that the message is properly formed if strings.Contains(message, terminator) { - return errors.Errorf("Message cannot contain '%s'", terminator) + return 0, errors.Errorf("Message cannot contain '%s'", terminator) } + //denote if this is a resend of an old request + resend := false + //lookup if an ongoing request is occurring - rqType, _, _, err := storage.Auth().GetRequest(partner.ID) - if err != nil && strings.Contains(err.Error(), auth.NoRequest) { - err = nil - } - if err != nil { + rqType, sr, _, err := storage.Auth().GetRequest(partner.ID) + + if err != nil && !strings.Contains(err.Error(), auth.NoRequest){ if rqType == auth.Receive { - return errors.WithMessage(err, - "Cannot send a request after "+ - "receiving a request") + return 0, errors.WithMessage(err, + "Cannot send a request after receiving a request") } else if rqType == auth.Sent { - return errors.WithMessage(err, - "Cannot send a request after "+ - "already sending one") + resend = true + }else{ + return 0, errors.WithMessage(err, + "Cannot send a request after receiving unknown error " + + "on requesting contact status") } } @@ -76,7 +76,7 @@ func RequestAuth(partner, me contact.Contact, message string, rng io.Reader, ecrFmt := newEcrFormat(baseFmt.GetEcrPayloadLen()) requestFmt, err := newRequestFormat(ecrFmt) if err != nil { - return errors.Errorf("failed to make request format: %+v", err) + return 0, errors.Errorf("failed to make request format: %+v", err) } //check the payload fits @@ -85,7 +85,7 @@ func RequestAuth(partner, me contact.Contact, message string, rng io.Reader, msgPayloadBytes := []byte(msgPayload) if len(msgPayloadBytes) > requestFmt.MsgPayloadLen() { - return errors.Errorf("Combined message longer than space "+ + return 0, errors.Errorf("Combined message longer than space "+ "available in payload; available: %v, length: %v", requestFmt.MsgPayloadLen(), len(msgPayloadBytes)) } @@ -95,17 +95,27 @@ func RequestAuth(partner, me contact.Contact, message string, rng io.Reader, salt := make([]byte, saltSize) _, err = rng.Read(salt) if err != nil { - return errors.Wrap(err, "Failed to generate salt") + return 0, errors.Wrap(err, "Failed to generate salt") + } + + var newPrivKey, newPubKey *cyclic.Int + + // in this case we have an ongoing request so we can resend the extant + // request + if resend{ + newPrivKey = sr.GetMyPrivKey() + newPubKey = sr.GetMyPubKey() + //in this case it is a new request and we must generate new keys + }else{ + //generate new keypair + newPrivKey = diffieHellman.GeneratePrivateKey(256, grp, rng) + newPubKey = diffieHellman.GeneratePublicKey(newPrivKey, grp) } //generate ownership proof ownership := cAuth.MakeOwnershipProof(storage.E2e().GetDHPrivateKey(), partner.DhPubKey, storage.E2e().GetGroup()) - //generate new keypair - newPrivKey := diffieHellman.GeneratePrivateKey(256, grp, rng) - newPubKey := diffieHellman.GeneratePublicKey(newPrivKey, grp) - jww.TRACE.Printf("RequestAuth MYPUBKEY: %v", newPubKey.Bytes()) jww.TRACE.Printf("RequestAuth THEIRPUBKEY: %v", partner.DhPubKey.Bytes()) @@ -130,67 +140,30 @@ func RequestAuth(partner, me contact.Contact, message string, rng io.Reader, /*store state*/ //fixme: channel is bricked if the first store succedes but the second fails //store the in progress auth - err = storage.Auth().AddSent(partner.ID, partner.DhPubKey, newPrivKey, - newPrivKey, confirmFp) - if err != nil { - return errors.Errorf("Failed to store auth request: %s", err) + if !resend{ + err = storage.Auth().AddSent(partner.ID, partner.DhPubKey, newPrivKey, + newPrivKey, confirmFp) + if err != nil { + return 0, errors.Errorf("Failed to store auth request: %s", err) + } } - //store the message as a critical message so it will always be sent - storage.GetCriticalRawMessages().AddProcessing(cmixMsg, partner.ID) + jww.INFO.Printf("Requesting Auth with %s, msgDigest: %s", + partner.ID, cmixMsg.Digest()) - go func() { - jww.INFO.Printf("Requesting Auth with %s, msgDigest: %s", - partner.ID, cmixMsg.Digest()) - - /*send message*/ - round, _, err := net.SendCMIX(cmixMsg, partner.ID, - params.GetDefaultCMIX()) - if err != nil { - // if the send fails just set it to failed, it will - // but automatically retried - jww.WARN.Printf("Auth Request with %s (msgDigest: %s)"+ - " failed to transmit: %+v", partner.ID, - cmixMsg.Digest(), err) - storage.GetCriticalRawMessages().Failed(cmixMsg, - partner.ID) - } + /*send message*/ + round, _, err := net.SendCMIX(cmixMsg, partner.ID, + params.GetDefaultCMIX()) + if err != nil { + // if the send fails just set it to failed, it will + // but automatically retried + return 0, errors.WithMessagef(err, "Auth Request with %s " + + "(msgDigest: %s) failed to transmit: %+v", partner.ID, + cmixMsg.Digest(), err) + } - jww.INFO.Printf("Auth Request with %s (msgDigest: %s) sent"+ - " on round %d", partner.ID, cmixMsg.Digest(), round) - - /*check message delivery*/ - sendResults := make(chan ds.EventReturn, 1) - roundEvents := net.GetInstance().GetRoundEvents() - - roundEvents.AddRoundEventChan(round, sendResults, 1*time.Minute, - states.COMPLETED, states.FAILED) - - success, numFailed, _ := utility.TrackResults(sendResults, 1) - if !success { - if numFailed > 0 { - jww.WARN.Printf("Auth Request with %s "+ - "(msgDigest: %s) failed "+ - "delivery due to round failure, "+ - "will retry on reconnect", - partner.ID, cmixMsg.Digest()) - } else { - jww.WARN.Printf("Auth Request with %s "+ - "(msgDigest: %s) failed "+ - "delivery due to timeout, "+ - "will retry on reconnect", - partner.ID, cmixMsg.Digest()) - } - storage.GetCriticalRawMessages().Failed(cmixMsg, - partner.ID) - } else { - jww.INFO.Printf("Auth Request with %s (msgDigest: %s) "+ - "delivered sucessfully", partner.ID, - cmixMsg.Digest()) - storage.GetCriticalRawMessages().Succeeded(cmixMsg, - partner.ID) - } - }() + jww.INFO.Printf("Auth Request with %s (msgDigest: %s) sent"+ + " on round %d", partner.ID, cmixMsg.Digest(), round) - return nil + return round, nil } diff --git a/bindings/authenticatedChannels.go b/bindings/authenticatedChannels.go index 28784c87f944bd3d15e43b873194f968dd513971..30b12f0a1b3039fba5b18eafeff874cc7a438a60 100644 --- a/bindings/authenticatedChannels.go +++ b/bindings/authenticatedChannels.go @@ -27,19 +27,20 @@ func (c *Client) MakePrecannedAuthenticatedChannel(precannedID int) (*Contact, e // authenticated channel // It will not run if the network status is not healthy // An error will be returned if a channel already exists, if a request was -// already received, or if a request was already sent +// already received. // When a confirmation occurs, the channel will be created and the callback // will be called +// This can be called many times and retried. // // This function takes the marshaled send report to ensure a memory leak does // not occur as a result of both sides of the bindings holding a refrence to // the same pointer. func (c *Client) RequestAuthenticatedChannel(recipientMarshaled, - meMarshaled []byte, message string) error { + meMarshaled []byte, message string) (int, error) { recipent, err := contact.Unmarshal(recipientMarshaled) if err != nil { - return errors.New(fmt.Sprintf("Failed to "+ + return 0, errors.New(fmt.Sprintf("Failed to "+ "RequestAuthenticatedChannel: Failed to Unmarshal Recipent: "+ "%+v", err)) } @@ -47,11 +48,13 @@ func (c *Client) RequestAuthenticatedChannel(recipientMarshaled, me, err := contact.Unmarshal(meMarshaled) if err != nil { - return errors.New(fmt.Sprintf("Failed to "+ + return 0, errors.New(fmt.Sprintf("Failed to "+ "RequestAuthenticatedChannel: Failed to Unmarshal Me: %+v", err)) } - return c.api.RequestAuthenticatedChannel(recipent, me, message) + rid, err := c.api.RequestAuthenticatedChannel(recipent, me, message) + + return int(rid), err } // RegisterAuthCallbacks registers both callbacks for authenticated channels. @@ -79,19 +82,26 @@ func (c *Client) RegisterAuthCallbacks(request AuthRequestCallback, // received request and sends a message to the requestor that the request has // been confirmed // It will not run if the network status is not healthy -// An error will be returned if a channel already exists, if a request doest +// An error will be returned if a request doest // exist, or if the passed in contact does not exactly match the received -// request -func (c *Client) ConfirmAuthenticatedChannel(recipientMarshaled []byte) error { +// request. +// This can be called many times and retried. +// +// This function takes the marshaled send report to ensure a memory leak does +// not occur as a result of both sides of the bindings holding a refrence to +// the same pointer. +func (c *Client) ConfirmAuthenticatedChannel(recipientMarshaled []byte) (int, error) { recipent, err := contact.Unmarshal(recipientMarshaled) if err != nil { - return errors.New(fmt.Sprintf("Failed to "+ + return 0, errors.New(fmt.Sprintf("Failed to "+ "ConfirmAuthenticatedChannel: Failed to Unmarshal Recipient: "+ "%+v", err)) } - return c.api.ConfirmAuthenticatedChannel(recipent) + rid, err := c.api.ConfirmAuthenticatedChannel(recipent) + + return int(rid), err } // VerifyOwnership checks if the ownership proof on a passed contact matches the diff --git a/bindings/callback.go b/bindings/callback.go index 34d26ecccaf4b4f105a2ab2e7b29d82fb1fe3728..a14cb5151358933f2f0bd6af6cdb04156ff4ac37 100644 --- a/bindings/callback.go +++ b/bindings/callback.go @@ -31,12 +31,19 @@ type NetworkHealthCallback interface { Callback(bool) } -// RoundEventHandler handles round events happening on the cMix network. +// RoundEventCallback handles waiting on the exact state of a round on +// the cMix network. type RoundEventCallback interface { EventCallback(rid, state int, timedOut bool) } -// RoundEventHandler handles round events happening on the cMix network. +// RoundCompletionCallback is returned when the completion of a round is known. +type RoundCompletionCallback interface { + EventCallback(rid int, success, timedOut bool) +} + +// MessageDeliveryCallback gets called on the determination if all events +// related to a message send were successful. type MessageDeliveryCallback interface { EventCallback(msgID []byte, delivered, timedOut bool, roundResults []byte) } diff --git a/bindings/client.go b/bindings/client.go index 34fb521076fca455294824ef3104f1c67445669d..b2c94fa95f46926ae891a8e9a41cb794cbdb28c8 100644 --- a/bindings/client.go +++ b/bindings/client.go @@ -333,18 +333,35 @@ func (c *Client) RegisterRoundEventsHandler(rid int, cb RoundEventCallback, return newRoundUnregister(roundID, ec, c.api.GetRoundEvents()) } -// RegisterMessageDeliveryCB allows the caller to get notified if the rounds a -// message was sent in successfully completed. Under the hood, this uses the same -// interface as RegisterRoundEventsHandler, but provides a convenient way to use -// the interface in its most common form, looking up the result of message -// retrieval +// WaitForRoundCompletion allows the caller to get notified if a round +// has completed (or failed). Under the hood, this uses an API which uses the internal +// round data, network historical round lookup, and waiting on network events +// to determine what has (or will) occur. +// +// The callbacks will return at timeoutMS if no state update occurs +func (c *Client) WaitForRoundCompletion(roundID int, + rec RoundCompletionCallback, timeoutMS int) error { + + f := func(allRoundsSucceeded, timedOut bool, rounds map[id.Round]api.RoundResult) { + rec.EventCallback(roundID, allRoundsSucceeded, timedOut) + } + + timeout := time.Duration(timeoutMS) * time.Millisecond + + return c.api.GetRoundResults([]id.Round{id.Round(roundID)}, timeout, f) +} + +// WaitForMessageDelivery allows the caller to get notified if the rounds a +// message was sent in successfully completed. Under the hood, this uses an API +// which uses the internal round data, network historical round lookup, and +// waiting on network events to determine what has (or will) occur. // // The callbacks will return at timeoutMS if no state update occurs // // This function takes the marshaled send report to ensure a memory leak does // not occur as a result of both sides of the bindings holding a reference to // the same pointer. -func (c *Client) WaitForRoundCompletion(marshaledSendReport []byte, +func (c *Client) WaitForMessageDelivery(marshaledSendReport []byte, mdc MessageDeliveryCallback, timeoutMS int) error { sr, err := UnmarshalSendReport(marshaledSendReport) diff --git a/cmd/getndf.go b/cmd/getndf.go index 914412093c8f1247953473c29a02edcd68346235..7ac150020c911d695f646993e3a409c18d6cb8da 100644 --- a/cmd/getndf.go +++ b/cmd/getndf.go @@ -18,11 +18,13 @@ import ( // "gitlab.com/elixxir/client/switchboard" // "gitlab.com/elixxir/client/ud" // "gitlab.com/elixxir/primitives/fact" + "gitlab.com/elixxir/client/api" "gitlab.com/elixxir/comms/client" "gitlab.com/xx_network/comms/connect" //"time" pb "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" "gitlab.com/xx_network/primitives/utils" ) @@ -63,12 +65,14 @@ var getNDFCmd = &cobra.Command{ if gwHost != "" { host, _ := connect.NewHost(&id.TempGateway, gwHost, cert, params) + dummyID := ephemeral.ReservedIDs[0] pollMsg := &pb.GatewayPoll{ Partial: &pb.NDFHash{ Hash: nil, }, LastUpdate: uint64(0), - ReceptionID: id.DummyUser.Marshal(), + ReceptionID: dummyID[:], + ClientVersion: []byte(api.SEMVER), } resp, err := comms.SendPoll(host, pollMsg) if err != nil { diff --git a/cmd/init.go b/cmd/init.go index cf19521f066d1e9e878524a7ee75fd3471cf6b7c..b00f215475bfba0c513752b1d2fd01fd13cea5c8 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -11,6 +11,7 @@ package cmd import ( "fmt" "github.com/spf13/cobra" + "github.com/spf13/viper" jww "github.com/spf13/jwalterweatherman" ) @@ -29,5 +30,9 @@ var initCmd = &cobra.Command{ } func init() { + initCmd.Flags().StringP("userid-prefix", "", "", + "Desired prefix of userID to brute force when running init command. Prepend (?i) for case-insensitive. Only Base64 characters are valid.") + _ = viper.BindPFlag("userid-prefix", initCmd.Flags().Lookup("userid-prefix")) + rootCmd.AddCommand(initCmd) } diff --git a/cmd/root.go b/cmd/root.go index c6ea6c91425d2cf96f532287dfcbfdc387fc7639..9ff887dad0dbb4d3d6e22a649f4ca43e5039a32e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -95,7 +95,7 @@ var rootCmd = &cobra.Command{ requestor contact.Contact, message string) { jww.INFO.Printf("Channel Request: %s", requestor.ID) - err := client.ConfirmAuthenticatedChannel( + _, err := client.ConfirmAuthenticatedChannel( requestor) if err != nil { jww.FATAL.Panicf("%+v", err) @@ -306,7 +306,7 @@ func createClient() *api.Client { storeDir := viper.GetString("session") regCode := viper.GetString("regcode") precannedID := viper.GetUint("sendid") - + userIDprefix := viper.GetString("userid-prefix") //create a new client if none exist if _, err := os.Stat(storeDir); os.IsNotExist(err) { // Load NDF @@ -320,8 +320,14 @@ func createClient() *api.Client { err = api.NewPrecannedClient(precannedID, string(ndfJSON), storeDir, []byte(pass)) } else { - err = api.NewClient(string(ndfJSON), storeDir, + if userIDprefix != "" { + err = api.NewVanityClient(string(ndfJSON), storeDir, + []byte(pass), regCode, userIDprefix) + } else { + err = api.NewClient(string(ndfJSON), storeDir, []byte(pass), regCode) + } + } if err != nil { @@ -399,7 +405,7 @@ func acceptChannel(client *api.Client, recipientID *id.ID) { if err != nil { jww.FATAL.Panicf("%+v", err) } - err = client.ConfirmAuthenticatedChannel( + _, err = client.ConfirmAuthenticatedChannel( recipientContact) if err != nil { jww.FATAL.Panicf("%+v", err) @@ -461,7 +467,7 @@ func addAuthenticatedChannel(client *api.Client, recipientID *id.ID, me := client.GetUser().GetContact() jww.INFO.Printf("Requesting auth channel from: %s", recipientID) - err := client.RequestAuthenticatedChannel(recipientContact, + _, err := client.RequestAuthenticatedChannel(recipientContact, me, msg) if err != nil { jww.FATAL.Panicf("%+v", err) diff --git a/cmd/single.go b/cmd/single.go index 7fe8e2038ef34240bd2ee363e6f38d8d94d26ed9..15f803b11bec0f775845fdfdfb9f8292c75ea2a7 100644 --- a/cmd/single.go +++ b/cmd/single.go @@ -55,7 +55,7 @@ var singleCmd = &cobra.Command{ authMgr.AddGeneralRequestCallback(func( requester contact.Contact, message string) { jww.INFO.Printf("Got request: %s", requester.ID) - err := client.ConfirmAuthenticatedChannel(requester) + _, err := client.ConfirmAuthenticatedChannel(requester) if err != nil { jww.FATAL.Panicf("%+v", err) } diff --git a/cmd/ud.go b/cmd/ud.go index ccefe891a4e87c2053ec62fd95e835115aa21a45..38cbecb2cd1c6572cd140b158f435fe4291500cd 100644 --- a/cmd/ud.go +++ b/cmd/ud.go @@ -55,7 +55,7 @@ var udCmd = &cobra.Command{ authMgr.AddGeneralRequestCallback(func( requester contact.Contact, message string) { jww.INFO.Printf("Got Request: %s", requester.ID) - err := client.ConfirmAuthenticatedChannel(requester) + _, err := client.ConfirmAuthenticatedChannel(requester) if err != nil { jww.FATAL.Panicf("%+v", err) }