seekia/internal/profiles/readProfiles/readProfiles.go

823 lines
27 KiB
Go
Raw Normal View History

// readProfiles provides functions to read and validate user profiles
package readProfiles
import "seekia/internal/cryptography/blake3"
import "seekia/internal/cryptography/edwardsKeys"
import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/profiles/profileFormat"
import messagepack "github.com/vmihailenco/msgpack/v5"
import "strings"
import "encoding/binary"
import "errors"
import "slices"
// Verifies a profile
//Outputs:
// -bool: Is valid
// -error: Will return error if there is a bug in the function
func VerifyProfile(inputProfile []byte)(bool, error){
ableToRead, _, _, _, _, _, _, err := ReadProfile(true, inputProfile)
if (err != nil) { return false, err }
if (ableToRead == false){
return false, nil
}
return true, nil
}
func VerifyProfileHash(inputHash [28]byte, profileTypeProvided bool, expectedProfileType string, isDisabledProvided bool, expectedIsDisabled bool)(bool, error){
if (profileTypeProvided == true){
if (expectedProfileType != "Mate" && expectedProfileType != "Host" && expectedProfileType != "Moderator"){
return false, errors.New("VerifyProfileHash called with invalid provided expectedProfileType: " + expectedProfileType)
}
}
profileType, profileIsDisabled, err := ReadProfileHashMetadata(inputHash)
if (err != nil){
return false, nil
}
if (profileTypeProvided == true){
if (profileType != expectedProfileType){
return false, nil
}
}
if (isDisabledProvided == true){
if (profileIsDisabled != expectedIsDisabled){
return false, nil
}
}
return true, nil
}
func VerifyAttributeHash(inputHash [27]byte, profileTypeProvided bool, expectedProfileType string, isCanonicalProvided bool, expectedIsCanonical bool)(bool, error){
if (profileTypeProvided == true){
if (expectedProfileType != "Mate" && expectedProfileType != "Host" && expectedProfileType != "Moderator"){
return false, errors.New("VerifyAttributeHash called with invalid provided expectedProfileType: " + expectedProfileType)
}
}
profileType, attributeIsCanonical, err := ReadAttributeHashMetadata(inputHash)
if (err != nil){
return false, nil
}
if (profileTypeProvided == true){
if (profileType != expectedProfileType){
return false, nil
}
}
if (isCanonicalProvided == true){
if (attributeIsCanonical != expectedIsCanonical){
return false, nil
}
}
return true, nil
}
//Outputs:
// -string: Profile Type
// -bool: Profile is disabled
// -error:
func ReadProfileHashMetadata(profileHash [28]byte)(string, bool, error){
metadataByte := profileHash[27]
switch metadataByte{
case 1:{
return "Mate", true, nil
}
case 2:{
return "Mate", false, nil
}
case 3:{
return "Host", true, nil
}
case 4:{
return "Host", false, nil
}
case 5:{
return "Moderator", true, nil
}
case 6:{
return "Moderator", false, nil
}
}
profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])
return "", false, errors.New("ReadProfileHashMetadata called with invalid profileHash: " + profileHashHex)
}
//Outputs:
// -string: Identity Type of author
// -bool: Attribute is canonical
// -error:
func ReadAttributeHashMetadata(attributeHash [27]byte)(string, bool, error){
metadataByte := attributeHash[26]
switch metadataByte{
case 1:{
return "Mate", true, nil
}
case 2:{
return "Mate", false, nil
}
case 3:{
return "Host", true, nil
}
case 4:{
return "Host", false, nil
}
case 5:{
return "Moderator", true, nil
}
case 6:{
return "Moderator", false, nil
}
}
attributeHashHex := encoding.EncodeBytesToHexString(attributeHash[:])
return "", false, errors.New("ReadAttributeHashMetadata called with invalid attributeHash: " + attributeHashHex)
}
// This function will read a profile and compute its hash.
//Outputs:
// -bool: Able to read profile
// -[28]byte: Profile hash
// -int: Profile version
// -byte: Network type (1 == Mainnet, 2 == Testnet1)
// -[16]byte: Profile author identity hash
// -int64: Profile broadcast time (alleged, can be faked)
// -bool: Profile is disabled
// -map[int]messagepack.RawMessage: Raw profile map (Attribute Identifier -> Attribute messagepack bytes value)
// -error (will return err if there is a bug)
func ReadProfileAndHash(verifyProfile bool, inputProfile []byte)(bool, [28]byte, int, byte, [16]byte, int64, bool, map[int]messagepack.RawMessage, error){
ableToRead, profileVersion, networkType, profileAuthor, profileBroadcastTime, profileIsDisabled, rawProfileMap, err := ReadProfile(verifyProfile, inputProfile)
if (err != nil) { return false, [28]byte{}, 0, 0, [16]byte{}, 0, false, nil, err }
if (ableToRead == false){
return false, [28]byte{}, 0, 0, [16]byte{}, 0, false, nil, nil
}
profileHashWithoutMetadataByte, err := blake3.GetBlake3HashAsBytes(27, inputProfile)
if (err != nil) { return false, [28]byte{}, 0, 0, [16]byte{}, 0, false, nil, err }
profileAuthorIdentityType, err := identity.GetIdentityTypeFromIdentityHash(profileAuthor)
if (err != nil) { return false, [28]byte{}, 0, 0, [16]byte{}, 0, false, nil, err }
getProfileHashMetadataByte := func()byte{
if (profileAuthorIdentityType == "Mate"){
if (profileIsDisabled == true){
return 1
}
return 2
} else if (profileAuthorIdentityType == "Host"){
if (profileIsDisabled == true){
return 3
}
return 4
}
// profileAuthorIdentityType == "Moderator"
if (profileIsDisabled == true){
return 5
}
return 6
}
profileHashMetadataByte := getProfileHashMetadataByte()
profileHashSlice := append(profileHashWithoutMetadataByte, profileHashMetadataByte)
profileHash := [28]byte(profileHashSlice)
return true, profileHash, profileVersion, networkType, profileAuthor, profileBroadcastTime, profileIsDisabled, rawProfileMap, nil
}
// This function reads a profile without computing the profile's hash
// This is faster because the profile's bytes do not need to be hashed
//Outputs:
// -bool: Able to read profile
// -int: Profile version
// -byte: Network type (1 == Mainnet, 2 == Testnet1)
// -[16]byte: Profile author identity hash
// -int64: Profile broadcast time (alleged, can be faked)
// -bool: Profile is disabled
// -map[int]messagepack.RawMessage: Raw profile map (Attribute identifier -> Attribute raw bytes)
// -error (will return err if there is a bug)
func ReadProfile(verifyProfile bool, inputProfile []byte)(bool, int, byte, [16]byte, int64, bool, map[int]messagepack.RawMessage, error){
var profileSlice []messagepack.RawMessage
err := messagepack.Unmarshal(inputProfile, &profileSlice)
if (err != nil){
// Profile is malformed: Invalid profile messagepack
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
if (len(profileSlice) != 2){
// Profile is malformed: Invalid profile messagepack
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
profileSignatureEncoded := profileSlice[0]
profileContentEncoded := profileSlice[1]
profileSignature, err := encoding.DecodeRawMessagePackTo64ByteArray(profileSignatureEncoded)
if (err != nil){
// Profile is malformed: Invalid profile signature
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
// This map will contain the profile content
// The fields are encoded as identifiers, which need to be converted to names
// Identifiers are integers, which we use instead of encoding the full name of the attribute
// We do this to make profiles smaller
// For example, 3 == "ProfileType"
rawProfileMap := make(map[int]messagepack.RawMessage)
err = encoding.DecodeMessagePackBytes(false, profileContentEncoded, &rawProfileMap)
if (err != nil) {
// Profile is malformed: Profile content map is invalid
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
profileVersionEncoded, exists := rawProfileMap[1]
if (exists == false) {
// Profile is malformed: Missing profileVersion
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
profileVersion, err := encoding.DecodeRawMessagePackToInt(profileVersionEncoded)
if (err != nil){
// Profile is malformed: Invalid profile version
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
if (profileVersion != 1){
// We cannot read this profile. It was created by an newer version of Seekia.
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
networkTypeEncoded, exists := rawProfileMap[51]
if (exists == false) {
// Profile is malformed: Missing NetworkType
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
networkType, err := encoding.DecodeRawMessagePackToByte(networkTypeEncoded)
if (err != nil){
// Profile is malformed: Invalid network type
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
isValid := helpers.VerifyNetworkType(networkType)
if (isValid == false){
// Profile is malformed: Invalid network type
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
identityKeyEncoded, exists := rawProfileMap[2]
if (exists == false) {
// Profile is malformed: Missing identity key.
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
identityKey, err := encoding.DecodeRawMessagePackTo32ByteArray(identityKeyEncoded)
if (err != nil){
// Profile is malformed: Invalid identity key
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
profileTypeEncoded, exists := rawProfileMap[3]
if (exists == false) {
// Profile is malformed: missing ProfileType.
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
profileType, err := encoding.DecodeRawMessagePackToString(profileTypeEncoded)
if (err != nil){
// Profile is malformed: Invalid ProfileType.
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
if (profileType != "Mate" && profileType != "Host" && profileType != "Moderator"){
// Profile is malformed: Invalid ProfileType.
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
identityHash, err := identity.ConvertIdentityKeyToIdentityHash(identityKey, profileType)
if (err != nil) { return false, 0, 0, [16]byte{}, 0, false, nil, err }
broadcastTimeEncoded, exists := rawProfileMap[4]
if (exists == false) {
// Profile is malformed: missing BroadcastTime
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
broadcastTimeInt64, err := encoding.DecodeRawMessagePackToInt64(broadcastTimeEncoded)
if (err != nil) {
// Profile is malformed: Contains invalid BroadcastTime
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
if (verifyProfile == true){
isValid := helpers.VerifyBroadcastTime(broadcastTimeInt64)
if (isValid == false){
// Profile is malformed: Contains invalid BroadcastTime
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
}
if (verifyProfile == true){
contentHash, err := blake3.Get32ByteBlake3Hash(profileContentEncoded)
if (err != nil) { return false, 0, 0, [16]byte{}, 0, false, nil, err }
signatureIsValid := edwardsKeys.VerifySignature(identityKey, profileSignature, contentHash)
if (signatureIsValid == false) {
// Profile is malformed: Invalid signature.
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
}
isDisabledEncoded, exists := rawProfileMap[5]
if (exists == true){
// Profile is disabled.
isDisabledBool, err := encoding.DecodeRawMessagePackToBool(isDisabledEncoded)
if (err != nil){
// Profile is malformed: Invalid IsDisabled attribute.
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
if (isDisabledBool == false){
// Profile is malformed: Invalid Disabled attribute.
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
if (len(rawProfileMap) != 6){
// Profile is malformed: Invalid Disabled profile.
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
return true, profileVersion, networkType, identityHash, broadcastTimeInt64, true, rawProfileMap, nil
}
if (verifyProfile == true){
profileAttributeObjectsList, err := profileFormat.GetProfileAttributeObjectsList()
if (err != nil) { return false, 0, 0, [16]byte{}, 0, false, nil, err }
for _, attributeObject := range profileAttributeObjectsList{
attributeName := attributeObject.AttributeName
if (attributeName == "Disabled" || attributeName == "ProfileType" || attributeName == "IdentityKey" || attributeName == "ProfileVersion" || attributeName == "NetworkType" || attributeName == "BroadcastTime"){
// We already verified these attributes
continue
}
attributeProfileVersions := attributeObject.ProfileVersions
isRelevantVersion := slices.Contains(attributeProfileVersions, profileVersion)
if (isRelevantVersion == false){
// This attribute does not belong to this profile version
// Profiles created in this version do not have this attribute
continue
}
attributeProfileTypes, err := attributeObject.GetProfileTypes(profileVersion)
if (err != nil) { return false, 0, 0, [16]byte{}, 0, false, nil, err }
isRelevantProfileType := slices.Contains(attributeProfileTypes, profileType)
if (isRelevantProfileType == false){
continue
}
attributeIdentifier := attributeObject.AttributeIdentifier
attributeIsRequired, err := attributeObject.GetIsRequired(profileVersion)
if (err != nil) { return false, 0, 0, [16]byte{}, 0, false, nil, err }
attributeValueBytes, exists := rawProfileMap[attributeIdentifier]
if (exists == false){
if (attributeIsRequired == true){
// Profile is malformed: Profile is missing a required attribute
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
continue
}
attributeIsValid, attributeValueString, err := formatProfileAttributeRawMessagePackToString(attributeName, attributeValueBytes)
if (err != nil) { return false, 0, 0, [16]byte{}, 0, false, nil, err }
if (attributeIsValid == false){
// Profile is malformed: Profile contains an invalid attribute value
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
attributeValueIsValid, _, err := attributeObject.CheckValueFunction(profileVersion, profileType, attributeValueString)
if (err != nil){ return false, 0, 0, [16]byte{}, 0, false, nil, err }
if (attributeValueIsValid == false){
// Profile is malformed: Profile contains an invalid attribute value
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
if (attributeIsRequired == false){
// We make sure profile has all the mandatory attributes for this attribute
// We don't need to perform this check if the attribute is required
mandatoryAttributesList, err := attributeObject.GetMandatoryAttributes(profileVersion)
if (err != nil){ return false, 0, 0, [16]byte{}, 0, false, nil, err }
for _, mandatoryAttributeName := range mandatoryAttributesList{
mandatoryAttributeIdentifier, err := profileFormat.GetAttributeIdentifierFromAttributeName(mandatoryAttributeName)
if (err != nil){
return false, 0, 0, [16]byte{}, 0, false, nil, errors.New("GetMandatoryAttributes returning unknown attribute name: " + mandatoryAttributeName)
}
_, exists := rawProfileMap[mandatoryAttributeIdentifier]
if (exists == false){
// Profile is malformed: Missing a mandatory attribute.
return false, 0, 0, [16]byte{}, 0, false, nil, nil
}
}
}
}
}
return true, profileVersion, networkType, identityHash, broadcastTimeInt64, false, rawProfileMap, nil
}
// This function must be called on profiles which have already been verified
// Outputs:
// -bool: Attribute exists
// -string: Attribute Value (Formatted)
// -error
func GetFormattedProfileAttributeFromRawProfileMap(rawProfileMap map[int]messagepack.RawMessage, attributeName string)(bool, string, error){
attributeIdentifier, err := profileFormat.GetAttributeIdentifierFromAttributeName(attributeName)
if (err != nil) {
return false, "", errors.New("GetFormattedProfileAttributeFromRawProfileMap failed: " + err.Error())
}
attributeValueBytes, exists := rawProfileMap[attributeIdentifier]
if (exists == false){
return false, "", nil
}
attributeIsValid, attributeFormatted, err := formatProfileAttributeRawMessagePackToString(attributeName, attributeValueBytes)
if (err != nil){ return false, "", err }
if (attributeIsValid == false){
// Profile is malformed.
// This should never happen, because this function should only be called after the profile has been verified by ReadProfile
return false, "", errors.New("GetFormattedProfileAttributeFromRawProfileMap called with raw profile map containing invalid " + attributeName + " attribute value.")
}
return true, attributeFormatted, nil
}
// We use this function to convert attributes from raw MessagePack to string
//Outputs:
// -bool: Attribute is valid
// -Be aware that this function does not fully verify attributes
// -string: Attribute formatted
// -error
func formatProfileAttributeRawMessagePackToString(attributeName string, attributeValueBytes messagepack.RawMessage)(bool, string, error){
switch attributeName{
// Some attributes are encoded in special ways in MessagePack to save space
// For example, BroadcastTime is encoded as a int64 rather than a string
case "ProfileVersion":{
profileVersion, err := encoding.DecodeRawMessagePackToInt(attributeValueBytes)
if (err != nil){
// Profile is malformed: Invalid profile version
return false, "", nil
}
if (profileVersion != 1){
// We cannot read this profile. It was created by an newer version of Seekia.
// This should never happen, because this function should only be called after the profile's network type has been verified
return false, "", errors.New("formatProfileAttributeRawMessagePackToString called with unknown ProfileVersion.")
}
return true, "1", nil
}
case "NetworkType":{
networkType, err := encoding.DecodeRawMessagePackToByte(attributeValueBytes)
if (err != nil){
// Profile is malformed: Invalid network type
// This should never happen, because this function should only be called after the profile's networkType has been verified
return false, "", errors.New("formatProfileAttributeRawMessagePackToString called with invalid NetworkType.")
}
isValid := helpers.VerifyNetworkType(networkType)
if (isValid == false){
// Profile malformed: Invalid network type
// This should never happen, because this function should only be called after the profile's networkType has been verified
return false, "", errors.New("formatProfileAttributeRawMessagePackToString called with invalid NetworkType.")
}
networkTypeString := helpers.ConvertByteToString(networkType)
return true, networkTypeString, nil
}
case "IdentityKey":{
identityKey, err := encoding.DecodeRawMessagePackTo32ByteArray(attributeValueBytes)
if (err != nil){
// Profile is malformed: Invalid identity key
// This should never happen, because this function should only be called after the profile's IdentityKey has been verified
return false, "", errors.New("formatProfileAttributeRawMessagePackToString called with invalid IdentityKey.")
}
identityKeyHexString := encoding.EncodeBytesToHexString(identityKey[:])
return true, identityKeyHexString, nil
}
case "BroadcastTime":{
broadcastTimeInt64, err := encoding.DecodeRawMessagePackToInt64(attributeValueBytes)
if (err != nil) {
// Profile is malformed: Contains invalid BroadcastTime
// This should never happen, because this function should only be called after the profile's BroadcastTime has been verified
return false, "", errors.New("formatProfileAttributeRawMessagePackToString called with invalid BroadcastTime.")
}
broadcastTimeString := helpers.ConvertInt64ToString(broadcastTimeInt64)
return true, broadcastTimeString, nil
}
case "Disabled":{
isDisabledBool, err := encoding.DecodeRawMessagePackToBool(attributeValueBytes)
if (err != nil){
// Profile is malformed: Contains invalid Disabled attribute
// This should never happen, because this function should only be called after the profile's Disabled has been verified
return false, "", errors.New("formatProfileAttributeRawMessagePackToString called with invalid Disabled.")
}
if (isDisabledBool == false){
// Profile is malformed: Contains invalid Disabled attribute
// This should never happen, because this function should only be called after the profile's Disabled has been verified
return false, "", errors.New("formatProfileAttributeRawMessagePackToString called with invalid Disabled.")
}
return true, "Yes", nil
}
case "NaclKey", "KyberKey":{
keyBytes, err := encoding.DecodeRawMessagePackToBytes(attributeValueBytes)
if (err != nil) {
// Profile is malformed: Contains invalid NaclKey/KyberKey attribute
return false, "", nil
}
keyString := encoding.EncodeBytesToBase64String(keyBytes)
return true, keyString, nil
}
case "Photos":{
var rawPhotoBytesList [][]byte
err := encoding.DecodeMessagePackBytes(false, attributeValueBytes, &rawPhotoBytesList)
if (err != nil) {
// Profile is malformed: Contains invalid Photos attribute
return false, "", nil
}
if (len(rawPhotoBytesList) == 0){
// Profile is malformed: Contains invalid Photos attribute
return false, "", nil
}
// We use this to build the output
// The output is a "+" delimited string of each photo's bytes encoded in Base64
var attributeBuilder strings.Builder
finalIndex := len(rawPhotoBytesList) - 1
for index, photoBytes := range rawPhotoBytesList{
photoBase64 := encoding.EncodeBytesToBase64String(photoBytes)
attributeBuilder.WriteString(photoBase64)
if (index != finalIndex){
attributeBuilder.WriteString("+")
}
}
photoAttributeString := attributeBuilder.String()
return true, photoAttributeString, nil
}
}
// Attribute is not encoded in a special way.
// It is encoded as a unicode string.
attributeValueString, err := encoding.DecodeRawMessagePackToString(attributeValueBytes)
if (err != nil) { return false, "", err }
return true, attributeValueString, nil
}
// Attribute hashes are used for moderation
// A profile's attribute hash can be reviewed without reviewing the whole profile
// This function does not verify the raw profile map. The raw profile map must first be verified
// Outputs:
// -map[int][27]byte: Profile attribute hashes map (Attribute identifier -> Attribute hash)
// -bool: Profile is canonical (all attributes are canonical)
// -error
func GetProfileAttributeHashesMap(profileAuthor [16]byte, profileVersion int, profileNetworkType byte, rawProfileMap map[int]messagepack.RawMessage)(map[int][27]byte, bool, error){
profileType, err := identity.GetIdentityTypeFromIdentityHash(profileAuthor)
if (err != nil){
profileAuthorHex := encoding.EncodeBytesToHexString(profileAuthor[:])
return nil, false, errors.New("GetProfileAttributeHashesMap called with invalid profileAuthor: " + profileAuthorHex)
}
_, exists := rawProfileMap[5]
if (exists == true){
// Profile is disabled.
// A disabled profile has an empty attribute hashes map.
// All disabled profile are also canonical
emptyMap := make(map[int][27]byte)
return emptyMap, true, nil
}
// We use a prefix when creating attribute hashes
// The prefix starts with the string "profileattributehashsalt" decoded from base32 to bytes
attributeHashInputPrefix := []byte{124, 92, 84, 44, 128, 156, 226, 128, 210, 100, 56, 36, 121, 1, 115}
attributeHashInputPrefix = append(attributeHashInputPrefix, profileAuthor[:]...)
attributeHashInputPrefix = append(attributeHashInputPrefix, profileNetworkType)
// Map Structure: Attribute Identifier -> Attribute hash
profileAttributeHashesMap := make(map[int][27]byte)
// We set this variable to false if any attribute is not canonical
profileIsCanonical := true
for attributeIdentifier, attributeRawValueBytes := range rawProfileMap{
attributeObject, err := profileFormat.GetAttributeObjectFromAttributeIdentifier(attributeIdentifier)
if (err != nil) { return nil, false, err }
attributeName := attributeObject.AttributeName
if (attributeName == "ProfileType" || attributeName == "IdentityKey" || attributeName == "ProfileVersion" || attributeName == "NetworkType"){
// We don't need to store these attributes in the attribute hashes map
continue
}
//Outputs:
// -bool: Attribute is canonical
// -error
getAttributeIsCanonical := func()(bool, error){
getIsCanonicalFunction := attributeObject.GetIsCanonical
attributeIsCanonicalInfo, err := getIsCanonicalFunction(profileVersion)
if (err != nil) { return false, err }
if (attributeIsCanonicalInfo == "Always"){
return true, nil
}
if (attributeIsCanonicalInfo == "Never"){
return false, nil
}
// attributeIsCanonicalInfo == "Sometimes"
// We have to manually check to see if the attribute's value is canonical
valueIsValid, attributeValueString, err := formatProfileAttributeRawMessagePackToString(attributeName, attributeRawValueBytes)
if (err != nil) { return false, err }
if (valueIsValid == false){
// Profile contains an invalid attribute value
return false, errors.New("GetProfileAttributeHashesMap called with profile containing invalid attribute value for attribute: " + attributeName)
}
valueIsValid, attributeIsCanonical, err := attributeObject.CheckValueFunction(profileVersion, profileType, attributeValueString)
if (err != nil){ return false, err }
if (valueIsValid == false){
// Profile contains an invalid attribute value
return false, errors.New("GetProfileAttributeHashesMap called with profile containing invalid attribute value for attribute: " + attributeName + ". Attribute value: " + attributeValueString)
}
return attributeIsCanonical, nil
}
attributeIsCanonical, err := getAttributeIsCanonical()
if (err != nil) { return nil, false, err }
if (attributeIsCanonical == false){
profileIsCanonical = false
}
// We now create the attribute hash
// Each attribute hash represents an attribute value created by a specific author
//
// We use the messagePack encoded bytes to create the attribute hash
// I'm not sure, but it may be possible to encode the same value using multiple different messagepack encodings
// For example, a number could be encoded in two different ways using raw messagepack
// This would result in two different attribute hashes for the same attribute value
// This is fine
// It would only be a problem if an attribute hash represented two different attribute values
if (attributeIdentifier < 1 || attributeIdentifier > 4294967295){
return nil, false, errors.New("profileFormat contains invalid attribute identifier.")
}
attributeIdentifierUint32 := uint32(attributeIdentifier)
// We copy the prefix and append the attribute identifier and attribute raw value bytes
// We then feed that into blake3
hashInputBytes := slices.Clone(attributeHashInputPrefix)
hashInputBytes = binary.LittleEndian.AppendUint32(hashInputBytes, attributeIdentifierUint32)
hashInputBytes = append(hashInputBytes, attributeRawValueBytes...)
attributeHashWithoutMetadata, err := blake3.GetBlake3HashAsBytes(26, hashInputBytes)
if (err != nil) { return nil, false, err }
getMetadataByte := func()(byte, error){
if (profileType == "Mate"){
if (attributeIsCanonical == true){
return 1, nil
}
return 2, nil
}
if (profileType == "Host"){
if (attributeIsCanonical == true){
return 3, nil
}
return 4, nil
}
if (profileType == "Moderator"){
if (attributeIsCanonical == true){
return 5, nil
}
return 6, nil
}
return 0, errors.New("GetIdentityTypeFromIdentityHash returning invalid profileType: " + profileType)
}
attributeHashMetadataByte, err := getMetadataByte()
if (err != nil) { return nil, false, err }
attributeHashBytes := append(attributeHashWithoutMetadata, attributeHashMetadataByte)
attributeHash := [27]byte(attributeHashBytes)
profileAttributeHashesMap[attributeIdentifier] = attributeHash
}
return profileAttributeHashesMap, profileIsCanonical, nil
}