// createProfiles provides a function to create user profiles package createProfiles import "seekia/internal/appValues" import "seekia/internal/cryptography/blake3" import "seekia/internal/cryptography/edwardsKeys" import "seekia/internal/encoding" import "seekia/internal/helpers" import "seekia/internal/profiles/profileFormat" import "seekia/internal/profiles/readProfiles" import messagepack "github.com/vmihailenco/msgpack/v5" import "strings" import "time" import "errors" func CreateProfile(identityPublicKey [32]byte, identityPrivateKey [64]byte, inputProfileMap map[string]string)([]byte, error){ networkTypeString, exists := inputProfileMap["NetworkType"] if (exists == false){ return nil, errors.New("CreateProfile called with profileMap missing NetworkType") } networkType, err := helpers.ConvertNetworkTypeStringToByte(networkTypeString) if (err != nil){ return nil, errors.New("CreateProfile called with profileMap containing invalid NetworkType: " + networkTypeString) } profileType, exists := inputProfileMap["ProfileType"] if (exists == false) { return nil, errors.New("CreateProfile called with profile map missing profile type.") } if (profileType != "Mate" && profileType != "Host" && profileType != "Moderator"){ return nil, errors.New("CreateProfile called with profile map containing invalid profileType: " + profileType) } networkTypeEncoded, err := encoding.EncodeMessagePackBytes(networkType) if (err != nil){ return nil, err } profileTypeEncoded, err := encoding.EncodeMessagePackBytes(profileType) if (err != nil) { return nil, err } profileVersion, err := appValues.GetProfileVersion(profileType) if (err != nil) { return nil, err } profileVersionEncoded, err := encoding.EncodeMessagePackBytes(profileVersion) if (err != nil) { return nil, err } identityPublicKeyEncoded, err := encoding.EncodeMessagePackBytes(identityPublicKey) if (err != nil) { return nil, err } broadcastTime := time.Now().Unix() broadcastTimeEncoded, err := encoding.EncodeMessagePackBytes(broadcastTime) if (err != nil) { return nil, err } profileContentMap := map[int]messagepack.RawMessage{ 51: networkTypeEncoded, 1: profileVersionEncoded, 2: identityPublicKeyEncoded, 3: profileTypeEncoded, 4: broadcastTimeEncoded, } getProfileIsDisabledBool := func()bool{ iAmDisabled, exists := inputProfileMap["Disabled"] if (exists == true && iAmDisabled == "Yes"){ return true } return false } profileIsDisabled := getProfileIsDisabledBool() if (profileIsDisabled == true){ trueEncoded, err := encoding.EncodeMessagePackBytes(true) if (err != nil) { return nil, err } profileContentMap[5] = trueEncoded } if (profileIsDisabled == false){ for attributeName, attributeValue := range inputProfileMap{ if (attributeName == ""){ return nil, errors.New("CreateProfile called with profileMap containing empty key.") } if (attributeValue == ""){ return nil, errors.New("CreateProfile called with profileMap containing empty value.") } if (attributeName == "BroadcastTime" || attributeName == "ProfileVersion"){ // This function should be called with profile maps which do not contain these attributes. return nil, errors.New("CreateProfile called with profileMap containing unwanted attribute: " + attributeName) } if (attributeName == "NetworkType" || attributeName == "ProfileType" || attributeName == "Disabled"){ // These are attributes we already added to the profileContentMap continue } getAttributeEncodedBytes := func()(messagepack.RawMessage, error){ if (attributeName == "NaclKey" || attributeName == "KyberKey"){ // We encode the raw bytes of the keys to save space keyBytes, err := encoding.DecodeBase64StringToBytes(attributeValue) if (err != nil){ return nil, errors.New("CreateProfile called with profileMap containing invalid " + attributeName + ": " + attributeValue) } keyBytesEncoded, err := encoding.EncodeMessagePackBytes(keyBytes) if (err != nil){ return nil, err } return keyBytesEncoded, nil } if (attributeName == "Photos"){ // We encode the raw bytes a list of []byte base64PhotosList := strings.Split(attributeValue, "+") rawPhotoBytesList := make([][]byte, 0, len(base64PhotosList)) for _, base64Photo := range base64PhotosList{ base64Bytes, err := encoding.DecodeBase64StringToBytes(base64Photo) if (err != nil) { return nil, errors.New("CreateProfile called with profile containing invalid photos attribute: Item not Base64: " + base64Photo) } rawPhotoBytesList = append(rawPhotoBytesList, base64Bytes) } rawPhotoBytesListEncoded, err := encoding.EncodeMessagePackBytes(rawPhotoBytesList) if (err != nil){ return nil, err } return rawPhotoBytesListEncoded, nil } //TODO: Add more attributes // We can encode all numbers and hex-encoded strings using messagepack to reduce size // //TODO: Encode base pairs using a number // There are 36 total base pair possibilities // This will reduce the base pair encoded size by 1/3 // This will be significant once each profile has thousands of base pairs // We encode the attribute value as a string attributeEncoded, err := encoding.EncodeMessagePackBytes(attributeValue) if (err != nil){ return nil, err } return attributeEncoded, nil } attributeEncoded, err := getAttributeEncodedBytes() if (err != nil) { return nil, err } attributeIdentifier, err := profileFormat.GetAttributeIdentifierFromAttributeName(attributeName) if (err != nil){ return nil, errors.New("CreateProfile failed: " + err.Error()) } profileContentMap[attributeIdentifier] = attributeEncoded } } profileContentMessagepack, err := encoding.EncodeMessagePackBytes(profileContentMap) if (err != nil) { return nil, errors.New("CreateProfile failed: " + err.Error()) } contentHash, err := blake3.Get32ByteBlake3Hash(profileContentMessagepack) if (err != nil) { return nil, err } profileSignature := edwardsKeys.CreateSignature(identityPrivateKey, contentHash) signatureEncoded, err := encoding.EncodeMessagePackBytes(profileSignature) if (err != nil) { return nil, err } profileSlice := []messagepack.RawMessage{signatureEncoded, profileContentMessagepack} profileBytes, err := encoding.EncodeMessagePackBytes(profileSlice) if (err != nil) { return nil, err } profileIsValid, err := readProfiles.VerifyProfile(profileBytes) if (err != nil){ return nil, err } if (profileIsValid == false){ return nil, errors.New("CreateProfile failed: Invalid result profile.") } return profileBytes, nil }