diff --git a/api/client.go b/api/client.go
index e70241ec757547aa7c3c81291cad52dd70aec947..ce24670a9d5057f118c32445c3637e7474098bf0 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/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 c3f915d186e3a6884bcc2263cf08f78b2051a670..5497f066e231c3e1252ef4584b0d9cf490c417e4 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -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 {