// readReviews provides functions to read/verify moderator reviews package readReviews //TODO: Remove ReviewHash from ReadReview function outputs and create a seperate ReadReviewHash function // This will prevent hashing from being performed every time we read reviews import "seekia/internal/allowedText" 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/messaging/readMessages" import messagepack "github.com/vmihailenco/msgpack/v5" import "bytes" import "errors" func VerifyReview(inputReview []byte)(bool, error){ ableToRead, _, _, _, _, _, _, _, _, err := ReadReview(true, inputReview) if (err != nil){ return false, err } if (ableToRead == false){ return false, nil } return true, nil } func VerifyReviewHash(inputHash [29]byte, reviewTypeProvided bool, expectedReviewType string)(bool, error){ if (reviewTypeProvided == true){ if (expectedReviewType != "Identity" && expectedReviewType != "Profile" && expectedReviewType != "Attribute" && expectedReviewType != "Message"){ return false, errors.New("VerifyReviewHash called with invalid expectedReviewType: " + expectedReviewType) } } reviewType, err := GetReviewTypeFromReviewHash(inputHash) if (err != nil){ return false, nil } if (reviewTypeProvided == true){ if (reviewType != expectedReviewType){ return false, nil } } return true, nil } //Outputs: // -string: Review type // -error func GetReviewTypeFromReviewHash(inputHash [29]byte)(string, error){ metadataByte := inputHash[28] if (metadataByte == 1){ return "Identity", nil } if (metadataByte == 2){ return "Profile", nil } if (metadataByte == 3){ return "Attribute", nil } if (metadataByte == 4){ return "Message", nil } reviewHashHex := encoding.EncodeBytesToHexString(inputHash[:]) return "", errors.New("GetReviewTypeFromReviewHash called with invalid reviewHash: " + reviewHashHex) } // This function computes the review hash and returns it //Outputs: // -bool: Able to read // -[29]byte: Review Hash // -int: Review version // -byte: Network Type (1 == Mainnet, 2 == Testnet1) // -[16]byte: Identity Hash of reviewer (Review author) // -int64: Broadcast time (alleged, can be faked) // -string: Review type // -[]byte: Reviewed hash // -string: Review Verdict // -map[string]string: Review Map // -error (returns err if there is a bug in the code) func ReadReviewAndHash(verifyReview bool, inputReview []byte)(bool, [29]byte, int, byte, [16]byte, int64, string, []byte, string, map[string]string, error){ ableToRead, reviewVersion, reviewNetworkType, reviewAuthor, reviewBroadcastTime, reviewType, reviewedHash, reviewVerdict, reviewMap, err := ReadReview(verifyReview, inputReview) if (err != nil) { return false, [29]byte{}, 0, 0, [16]byte{}, 0, "", nil, "", nil, err } if (ableToRead == false){ return false, [29]byte{}, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } reviewHashWithoutMetadataByte, err := blake3.GetBlake3HashAsBytes(28, inputReview) if (err != nil) { return false, [29]byte{}, 0, 0, [16]byte{}, 0, "", nil, "", nil, err } getReviewHashMetadataByte := func()(byte, error){ if (reviewType == "Identity"){ return 1, nil } else if (reviewType == "Profile"){ return 2, nil } else if (reviewType == "Attribute"){ return 3, nil } else if (reviewType == "Message"){ return 4, nil } return 0, errors.New("ReadReview returning invalid reviewType: " + reviewType) } reviewHashMetadataByte, err := getReviewHashMetadataByte() if (err != nil) { return false, [29]byte{}, 0, 0, [16]byte{}, 0, "", nil, "", nil, err } reviewHashSlice := append(reviewHashWithoutMetadataByte, reviewHashMetadataByte) reviewHash := [29]byte(reviewHashSlice) return true, reviewHash, reviewVersion, reviewNetworkType, reviewAuthor, reviewBroadcastTime, reviewType, reviewedHash, reviewVerdict, reviewMap, nil } //TODO: Encode Verdict as an int to save space // This function does not compute the review hash, thus it is faster //Outputs: // -bool: Able to read // -int: Review version // -byte: Network Type (1 == Mainnet, 2 == Testnet1) // -[16]byte: Identity Hash of reviewer (Review author) // -int64: Broadcast time (alleged, can be faked) // -string: Review type // -[]byte: Reviewed hash // -string: Review Verdict // -map[string]string: Review Map // -error (returns err if there is a bug in the code) func ReadReview(verifyReview bool, inputReview []byte)(bool, int, byte, [16]byte, int64, string, []byte, string, map[string]string, error){ var reviewSlice []messagepack.RawMessage err := messagepack.Unmarshal(inputReview, &reviewSlice) if (err != nil) { // Invalid review: Malformed messagepack return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } if (len(reviewSlice) != 2){ // Invalid review: Malformed messagepack return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } signatureMessagepack := reviewSlice[0] reviewContentMessagepack := reviewSlice[1] reviewSignature, err := encoding.DecodeRawMessagePackTo64ByteArray(signatureMessagepack) if (err != nil) { // Invalid review: Malformed messagepack return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } reviewContentMap := make(map[int]messagepack.RawMessage) err = encoding.DecodeMessagePackBytes(false, reviewContentMessagepack, &reviewContentMap) if (err != nil) { // Invalid review: Malformed messagepack return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } reviewVersionMessagepack, exists := reviewContentMap[1] if (exists == false){ // Invalid Review: Missing ReviewVersion return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } reviewVersion, err := encoding.DecodeRawMessagePackToInt(reviewVersionMessagepack) if (err != nil) { // Invalid Review: Invalid review version return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } if (reviewVersion != 1){ // We cannot read this review. It was created by a newer version of Seekia. return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } reviewNetworkTypeMessagepack, exists := reviewContentMap[2] if (exists == false){ // Invalid Review: Missing NetworkType return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } reviewNetworkType, err := encoding.DecodeRawMessagePackToByte(reviewNetworkTypeMessagepack) if (err != nil){ // Invalid Review: Invalid network type messagepack return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } isValid := helpers.VerifyNetworkType(reviewNetworkType) if (isValid == false){ // Invalid Review: Invalid NetworkType return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } authorIdentityKeyMessagepack, exists := reviewContentMap[3] if (exists == false){ // Invalid Review: Missing IdentityKey return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } authorIdentityKey, err := encoding.DecodeRawMessagePackTo32ByteArray(authorIdentityKeyMessagepack) if (err != nil) { // Invalid Review: Invalid IdentityKey return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } if (verifyReview == true){ contentHash, err := blake3.Get32ByteBlake3Hash(reviewContentMessagepack) if (err != nil) { return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, err } isValid := edwardsKeys.VerifySignature(authorIdentityKey, reviewSignature, contentHash) if (isValid == false) { // Invalid review: Invalid signature return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } } authorIdentityHash, err := identity.ConvertIdentityKeyToIdentityHash(authorIdentityKey, "Moderator") if (err != nil) { return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, err } broadcastTimeMessagepack, exists := reviewContentMap[4] if (exists == false){ // Invalid Review: Missing BroadcastTime return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } broadcastTime, err := encoding.DecodeRawMessagePackToInt64(broadcastTimeMessagepack) if (err != nil) { // Invalid Review: Invalid BroadcastTime return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } reviewedHashMessagepack, exists := reviewContentMap[5] if (exists == false){ // Invalid Review: Missing ReviewedHash return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } reviewedHash, err := encoding.DecodeRawMessagePackToBytes(reviewedHashMessagepack) if (err != nil) { // Invalid Review: Invalid ReviewedHash return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } reviewType, err := helpers.GetReviewedTypeFromReviewedHash(reviewedHash) if (err != nil){ // Invalid Review: Invalid ReviewedHash return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } verdictMessagepack, exists := reviewContentMap[6] if (exists == false){ // Invalid Review: Missing Verdict return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } reviewVerdict, err := encoding.DecodeRawMessagePackToString(verdictMessagepack) if (err != nil) { // Invalid Review: Invalid Verdict return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } // These fields are not included within all reviews var reason string var messageCipherKeyBytes []byte reasonMessagepack, exists := reviewContentMap[7] if (exists == true){ err = messagepack.Unmarshal(reasonMessagepack, &reason) if (err != nil) { // Invalid Review: Invalid Reason return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } } if (reviewType == "Message"){ messageCipherKeyMessagepack, exists := reviewContentMap[8] if (exists == false){ // Invalid Review: Message review missing MessageCipherKey return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } err = messagepack.Unmarshal(messageCipherKeyMessagepack, &messageCipherKeyBytes) if (err != nil) { // Invalid Review: Invalid MessageCipherKey return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } } if (verifyReview == true){ isValid := helpers.VerifyBroadcastTime(broadcastTime) if (isValid == false){ // Invalid review: Review contains contains invalid BroadcastTime return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } if (reviewVerdict != "Ban" && reviewVerdict != "Approve" && reviewVerdict != "None"){ // Invalid review: Review contains invalid Verdict return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } if (reviewType == "Identity" && reviewVerdict == "Approve"){ // Invalid review: Review contains approve verdict for identity review return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } if (reviewType == "Identity"){ areEqual := bytes.Equal(reviewedHash, authorIdentityHash[:]) if (areEqual == true){ // Invalid review: Author cannot ban themselves. return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } //TODO: Verify ErrantProfiles, ErrantMessages, ErrantReviews, and ErrantAttributes } else if (reviewType == "Message"){ if (len(messageCipherKeyBytes) != 32){ // Invalid review: Message review contains invalid messageCipherKey return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } } if (reason != "") { if (len(reason) > 200) { // Invalid review: ReviewMap contains invalid reason return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } isAllowed := allowedText.VerifyStringIsAllowed(reason) if (isAllowed == false){ // Invalid review: ReviewMap contains invalid reason return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil } } } reviewNetworkTypeString := helpers.ConvertByteToString(reviewNetworkType) authorIdentityKeyHex := encoding.EncodeBytesToHexString(authorIdentityKey[:]) broadcastTimeString := helpers.ConvertInt64ToString(broadcastTime) reviewedHashString, err := helpers.EncodeReviewedHashBytesToString(reviewedHash) if (err != nil){ return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, errors.New("GetReviewedTypeFromReviewedHash not verifying reviewedHash.") } reviewMap := map[string]string{ "ReviewVersion": "1", "NetworkType": reviewNetworkTypeString, "IdentityKey": authorIdentityKeyHex, "BroadcastTime": broadcastTimeString, "ReviewedHash": reviewedHashString, "Verdict": reviewVerdict, } if (reason != ""){ reviewMap["Reason"] = reason } if (reviewType == "Message"){ messageCipherKey := encoding.EncodeBytesToHexString(messageCipherKeyBytes) reviewMap["MessageCipherKey"] = messageCipherKey } return true, 1, reviewNetworkType, authorIdentityHash, broadcastTime, reviewType, reviewedHash, reviewVerdict, reviewMap, nil } // This struct is used to represent a review and its hash // This is useful, because we do not need to hash each review to retrieve its hash type ReviewWithHash struct{ ReviewHash [29]byte ReviewBytes []byte } //Outputs: // -map[[16]byte]int64: Ban advocates map (identity hash -> Time of ban) // -error func GetIdentityBanAdvocatesMapFromReviewsList(reviewsList []ReviewWithHash, identityHash [16]byte, networkType byte)(map[[16]byte]int64, error){ checkIfReviewIsValid := func(_ map[string]string)(bool, error){ return true, nil } approveAdvocatesMap, banAdvocatesMap, err := getModeratorNewestVerdictMapsFromReviewsList(reviewsList, "Identity", identityHash[:], networkType, checkIfReviewIsValid) if (err != nil) { return nil, err } if (len(approveAdvocatesMap) != 0){ return nil, errors.New("getModeratorNewestVerdictMapsFromReviewsList returning non-empty approve advocates map for identity.") } return banAdvocatesMap, nil } // Outputs: // -map[[16]byte]int64: Approve advocate identity hash -> Time of approval // -map[[16]byte]int64: Ban advocate identity hash -> Time of ban // -error func GetMessageVerdictMapsFromReviewsList(reviewsList []ReviewWithHash, messageHash [26]byte, messageNetworkType byte, messageCipherKeyHash [25]byte)(map[[16]byte]int64, map[[16]byte]int64, error){ // We must check to make sure review has a valid messageCipherKey // This will function as a proof that the reviewer has seen the message // If the cipher key hash is valid but the key cannot decrypt the message, we know the reviewer and message author are both malicious // We can detect these kinds of reviews and automatically ban the authors verifyReviewIsValidFunction := func(reviewMap map[string]string)(bool, error){ isValid, err := VerifyMessageReviewCipherKey(reviewMap, messageCipherKeyHash) return isValid, err } approveAdvocatesMap, banAdvocatesMap, err := getModeratorNewestVerdictMapsFromReviewsList(reviewsList, "Message", messageHash[:], messageNetworkType, verifyReviewIsValidFunction) if (err != nil) { return nil, nil, err } return approveAdvocatesMap, banAdvocatesMap, nil } // This function is used to verify that message reviews are valid func VerifyMessageReviewCipherKey(reviewMap map[string]string, messageCipherKeyHash [25]byte)(bool, error){ reviewMessageCipherKey, exists := reviewMap["MessageCipherKey"] if (exists == false) { return false, errors.New("VerifyMessageReviewCipherKey called with message review missing MessageCipherKey") } reviewMessageCipherKeyArray, err := readMessages.ReadMessageCipherKeyHex(reviewMessageCipherKey) if (err != nil){ return false, errors.New("VerifyMessageReviewCipherKey called with message review containing invalid MessageCipherKey: " + reviewMessageCipherKey + ". Reason: " + err.Error()) } reviewCipherKeyHash, err := readMessages.ConvertMessageCipherKeyToCipherKeyHash(reviewMessageCipherKeyArray) if (err != nil){ return false, err } if (reviewCipherKeyHash != messageCipherKeyHash){ // Reviewer must be malicious. Skip review. return false, nil } return true, nil } //Outputs: // -map[[16]byte]int64: Approve advocates map (identity hash -> Time of approve) // -map[[16]byte]int64: Ban advocates map (identity hash -> Time of ban) // -error func GetProfileVerdictMapsFromReviewsList(reviewsList []ReviewWithHash, profileHash [28]byte, profileNetworkType byte)(map[[16]byte]int64, map[[16]byte]int64, error){ verifyReviewIsValid := func(_ map[string]string)(bool, error){ return true, nil } approveAdvocatesMap, banAdvocatesMap, err := getModeratorNewestVerdictMapsFromReviewsList(reviewsList, "Profile", profileHash[:], profileNetworkType, verifyReviewIsValid) if (err != nil) { return nil, nil, err } return approveAdvocatesMap, banAdvocatesMap, nil } //Outputs: // -map[[16]byte]int64: Approve advocates map (identity hash -> Time of approval) // -map[[16]byte]int64: Ban advocates map (identity hash -> Time of ban) // -error func GetProfileAttributeVerdictMapsFromReviewsList(reviewsList []ReviewWithHash, attributeHash [27]byte, attributeNetworkType byte)(map[[16]byte]int64, map[[16]byte]int64, error){ verifyReviewIsValid := func(_ map[string]string)(bool, error){ return true, nil } approveAdvocatesMap, banAdvocatesMap, err := getModeratorNewestVerdictMapsFromReviewsList(reviewsList, "Attribute", attributeHash[:], attributeNetworkType, verifyReviewIsValid) if (err != nil) { return nil, nil, err } return approveAdvocatesMap, banAdvocatesMap, nil } // This function takes a list of reviews and a reviewedHash and returns maps for each moderator's newest verdict ("Approve"/"Ban") // This requires keeping track of the newest review from each moderator. Moderators can undo reviews by using the None verdict. //Outputs: // -map[[16]byte]int64: Approve advocates map (identity hash -> Time approve review was submitted) // -map[[16]byte]int64: Ban advocates map (identity hash -> Time ban review was submitted) // -error func getModeratorNewestVerdictMapsFromReviewsList(reviewsList []ReviewWithHash, reviewType string, reviewedHash []byte, networkType byte, verifyReviewIsValid func(map[string]string)(bool, error))(map[[16]byte]int64, map[[16]byte]int64, error){ if (reviewType != "Identity" && reviewType != "Profile" && reviewType != "Attribute" && reviewType != "Message"){ return nil, nil, errors.New("getModeratorNewestVerdictMapsFromReviewsList called with invalid ReviewType: " + reviewType) } reviewedType, err := helpers.GetReviewedTypeFromReviewedHash(reviewedHash) if (err != nil){ reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash) return nil, nil, errors.New("getModeratorNewestVerdictMapsFromReviewsList called with invalid reviewedHash: " + reviewedHashHex) } if (reviewedType != reviewType){ return nil, nil, errors.New("getModeratorNewestVerdictMapsFromReviewsList called with reviewType that does not match reviewedHash") } isValid := helpers.VerifyNetworkType(networkType) if (isValid == false){ networkTypeString := helpers.ConvertByteToString(networkType) return nil, nil, errors.New("getModeratorNewestVerdictMapsFromReviewsList called with invalid networkType: " + networkTypeString) } // This stores info about a review type ReviewInfoObject struct{ ReviewHash [29]byte BroadcastTime int64 Verdict string } // This map stores info about each reviewer's newest review for the provided reviewedHash // Map Structure: Reviewer Identity Hash -> Newest Review Info Object newestReviewsMap := make(map[[16]byte]ReviewInfoObject) for _, reviewWithHash := range reviewsList{ reviewHash := reviewWithHash.ReviewHash reviewBytes := reviewWithHash.ReviewBytes ableToRead, _, reviewNetworkType, reviewAuthor, reviewBroadcastTime, currentReviewType, currentReviewedHash, reviewVerdict, reviewMap, err := ReadReview(false, reviewBytes) if (err != nil) { return nil, nil, err } if (ableToRead == false){ return nil, nil, errors.New("getModeratorNewestVerdictMapsFromReviewsList called with invalid review.") } if (reviewNetworkType != networkType){ continue } if (currentReviewType != reviewType){ continue } areEqual := bytes.Equal(currentReviewedHash, reviewedHash) if (areEqual == false){ continue } isValid, err := verifyReviewIsValid(reviewMap) if (err != nil) { return nil, nil, err } if (isValid == false){ continue } existingNewestReviewInfoObject, exists := newestReviewsMap[reviewAuthor] if (exists == true){ existingNewestReviewBroadcastTime := existingNewestReviewInfoObject.BroadcastTime if (existingNewestReviewBroadcastTime == reviewBroadcastTime){ // The reviewer must be malicious, or their computer clock was skewed somehow // We compare the review hashes to see which review to use // This is necessary so that a moderator's newest review will always be calculated the same way by all Seekia clients. existingNewestReviewHash := existingNewestReviewInfoObject.ReviewHash compareValue := bytes.Compare(reviewHash[:], existingNewestReviewHash[:]) if (compareValue == 0){ // This should not happen, because this function should be called with a list of unique reviews return nil, nil, errors.New("getModeratorNewestVerdictMapsFromReviewsList called with reviewsList containing duplicate review.") } if (compareValue == -1){ continue } } else if (existingNewestReviewBroadcastTime > reviewBroadcastTime){ // This review was overwritten/replaced by a newer review continue } } newNewestReviewInfoObject := ReviewInfoObject{ ReviewHash: reviewHash, BroadcastTime: reviewBroadcastTime, Verdict: reviewVerdict, } newestReviewsMap[reviewAuthor] = newNewestReviewInfoObject } // Map Structure: Identity hash -> Time of approve review approveAdvocatesMap := make(map[[16]byte]int64) // Map Structure: Identity hash -> Time of ban review banAdvocatesMap := make(map[[16]byte]int64) for reviewerIdentityHash, reviewerNewestReviewInfoObject := range newestReviewsMap{ newestReviewVerdict := reviewerNewestReviewInfoObject.Verdict reviewBroadcastTime := reviewerNewestReviewInfoObject.BroadcastTime if (newestReviewVerdict == "Approve"){ approveAdvocatesMap[reviewerIdentityHash] = reviewBroadcastTime continue } else if (newestReviewVerdict == "Ban"){ banAdvocatesMap[reviewerIdentityHash] = reviewBroadcastTime continue } else if (newestReviewVerdict == "None"){ continue } return nil, nil, errors.New("ReadReview returning invalid verdict: " + newestReviewVerdict) } return approveAdvocatesMap, banAdvocatesMap, nil } //Outputs: // -bool: Review exists (this means the moderator has banned the identity) // -[]byte: Newest Review bytes // -map[string]string: Newest Review map // -error func GetModeratorNewestIdentityReviewFromReviewsList(inputReviewsList []ReviewWithHash, moderatorIdentityHash [16]byte, reviewedIdentityHash [16]byte, networkType byte)(bool, []byte, map[string]string, error){ isValid := helpers.VerifyNetworkType(networkType) if (isValid == false){ networkTypeString := helpers.ConvertByteToString(networkType) return false, nil, nil, errors.New("GetModeratorNewestIdentityReviewFromReviewsList called with invalid networkType: " + networkTypeString) } checkIfReviewIsValid := func(_ map[string]string)(bool, error){ return true, nil } reviewExists, reviewBytes, reviewMap, _, _, err := getNewestModeratorReviewFromReviewsList(inputReviewsList, moderatorIdentityHash, "Identity", reviewedIdentityHash[:], networkType, checkIfReviewIsValid) return reviewExists, reviewBytes, reviewMap, err } //Outputs: // -bool: Review exists // -[]byte: Newest review bytes // -map[string]string: Newest review map // -string: Review verdict // -int64: Time of review // -error func GetModeratorNewestProfileReviewFromReviewsList(inputReviewsList []ReviewWithHash, moderatorIdentityHash [16]byte, profileHash [28]byte, profileNetworkType byte)(bool, []byte, map[string]string, string, int64, error){ isValid := helpers.VerifyNetworkType(profileNetworkType) if (isValid == false){ profileNetworkTypeString := helpers.ConvertByteToString(profileNetworkType) return false, nil, nil, "", 0, errors.New("GetModeratorNewestProfileReviewFromReviewsList called with invalid profileNetworkType: " + profileNetworkTypeString) } verifyReviewIsValidFunction := func(_ map[string]string)(bool, error){ return true, nil } reviewExists, reviewBytes, reviewMap, reviewVerdict, reviewBroadcastTime, err := getNewestModeratorReviewFromReviewsList(inputReviewsList, moderatorIdentityHash, "Profile", profileHash[:], profileNetworkType, verifyReviewIsValidFunction) return reviewExists, reviewBytes, reviewMap, reviewVerdict, reviewBroadcastTime, err } //Outputs: // -bool: Review exists // -[]byte: Newest review bytes // -map[string]string: Newest review map // -string: Newest Review verdict // -int64: Time of review // -error func GetModeratorNewestProfileAttributeReviewFromReviewsList(inputReviewsList []ReviewWithHash, moderatorIdentityHash [16]byte, attributeHash [27]byte, attributeNetworkType byte)(bool, []byte, map[string]string, string, int64, error){ isValid := helpers.VerifyNetworkType(attributeNetworkType) if (isValid == false){ attributeNetworkTypeString := helpers.ConvertByteToString(attributeNetworkType) return false, nil, nil, "", 0, errors.New("GetModeratorNewestProfileAttributeReviewFromReviewsList called with invalid attributeNetworkType: " + attributeNetworkTypeString) } verifyReviewIsValidFunction := func(_ map[string]string)(bool, error){ return true, nil } reviewExists, reviewBytes, reviewMap, reviewVerdict, verdictTime, err := getNewestModeratorReviewFromReviewsList(inputReviewsList, moderatorIdentityHash, "Attribute", attributeHash[:], attributeNetworkType, verifyReviewIsValidFunction) return reviewExists, reviewBytes, reviewMap, reviewVerdict, verdictTime, err } //Outputs: // -bool: Review exists // -[]byte: Newest review bytes // -map[string]string: Newest review map // -string: Review verdict // -error func GetModeratorNewestMessageReviewFromReviewsList(inputReviewsList []ReviewWithHash, moderatorIdentityHash [16]byte, messageHash [26]byte, messageNetworkType byte, messageCipherKeyHash [25]byte)(bool, []byte, map[string]string, string, error){ isValid := helpers.VerifyNetworkType(messageNetworkType) if (isValid == false){ messageNetworkTypeString := helpers.ConvertByteToString(messageNetworkType) return false, nil, nil, "", errors.New("GetModeratorNewestMessageReviewFromReviewsList called with invalid messageNetworkType: " + messageNetworkTypeString) } verifyReviewIsValidFunction := func(reviewMap map[string]string)(bool, error){ isValid, err := VerifyMessageReviewCipherKey(reviewMap, messageCipherKeyHash) return isValid, err } reviewExists, reviewBytes, reviewMap, reviewVerdict, _, err := getNewestModeratorReviewFromReviewsList(inputReviewsList, moderatorIdentityHash, "Message", messageHash[:], messageNetworkType, verifyReviewIsValidFunction) return reviewExists, reviewBytes, reviewMap, reviewVerdict, err } // This function takes input of a list of reviews // Returns newest review for a specified moderator, reviewedHash and reviewType // Will omit review with "None" verdict // Inputs: // -[][]byte: List of reviews created by moderator // -[16]byte: Identity hash of moderator // -byte: Network type // -string: Review Type // -[]byte: Reviewed hash // -func(map[string]string)(bool, error): Takes review map as input, returns if review is valid //Outputs: // -bool: Review exists (will be false if newest review is a None verdict) // -[]byte: Newest review bytes // -map[string]string: Newest review map // -string: Newest review verdict // -int64: Newest review broadcast time // -error func getNewestModeratorReviewFromReviewsList(inputReviewsList []ReviewWithHash, moderatorIdentityHash [16]byte, reviewType string, reviewedHash []byte, networkType byte, verifyReviewIsValid func(map[string]string)(bool, error))(bool, []byte, map[string]string, string, int64, error){ isValid, err := identity.VerifyIdentityHash(moderatorIdentityHash, true, "Moderator") if (err != nil) { return false, nil, nil, "", 0, err } if (isValid == false){ moderatorIdentityHashHex := encoding.EncodeBytesToHexString(moderatorIdentityHash[:]) return false, nil, nil, "", 0, errors.New("getNewestModeratorReviewFromReviewsList called with invalid moderatorIdentityHash: " + moderatorIdentityHashHex) } if (reviewType != "Identity" && reviewType != "Profile" && reviewType != "Attribute" && reviewType != "Message"){ return false, nil, nil, "", 0, errors.New("getNewestModeratorReviewFromReviewsList called with invalid reviewType: " + reviewType) } isValid = helpers.VerifyNetworkType(networkType) if (isValid == false){ networkTypeString := helpers.ConvertByteToString(networkType) return false, nil, nil, "", 0, errors.New("getNewestModeratorReviewFromReviewsList called with invalid networkType: " + networkTypeString) } anyReviewFound := false newestReviewHash := [29]byte{} newestReviewBytes := make([]byte, 0) newestReviewMap := make(map[string]string) newestReviewBroadcastTime := int64(0) newestReviewVerdict := "" for _, reviewWithHash := range inputReviewsList{ reviewHash := reviewWithHash.ReviewHash reviewBytes := reviewWithHash.ReviewBytes ableToRead, _, reviewNetworkType, reviewAuthor, reviewBroadcastTime, currentReviewType, currentReviewedHash, reviewVerdict, reviewMap, err := ReadReview(false, reviewBytes) if (err != nil) { return false, nil, nil, "", 0, err } if (ableToRead == false){ return false, nil, nil, "", 0, errors.New("getNewestModeratorReviewFromReviewsList called with invalid review") } if (reviewNetworkType != networkType){ continue } if (reviewAuthor != moderatorIdentityHash){ continue } if (currentReviewType != reviewType) { continue } areEqual := bytes.Equal(currentReviewedHash, reviewedHash) if (areEqual == false){ continue } reviewIsValid, err := verifyReviewIsValid(reviewMap) if (err != nil){ return false, nil, nil, "", 0, err } if (reviewIsValid == false){ continue } if (anyReviewFound == false || reviewBroadcastTime >= newestReviewBroadcastTime){ if (reviewBroadcastTime == newestReviewBroadcastTime){ // The reviewer must be malicious, or their computer clock was skewed somehow // We compare the review hashes to see which review to use // This is necessary so that a moderator's newest review will always be calculated the same way by all Seekia clients. compareValue := bytes.Compare(reviewHash[:], newestReviewHash[:]) if (compareValue == 0){ // This should not happen, because this function should be called with a list of unique reviews return false, nil, nil, "", 0, errors.New("getNewestModeratorReviewFromReviewsList called with reviewsList containing duplicate review.") } if (compareValue == -1){ continue } } anyReviewFound = true newestReviewHash = reviewHash newestReviewBytes = reviewBytes newestReviewMap = reviewMap newestReviewBroadcastTime = reviewBroadcastTime newestReviewVerdict = reviewVerdict } } if (anyReviewFound == false){ return false, nil, nil, "", 0, nil } if (newestReviewVerdict == "None"){ return false, nil, nil, "", 0, nil } return true, newestReviewBytes, newestReviewMap, newestReviewVerdict, newestReviewBroadcastTime, nil } // This function returns a map of reviews created by a particular moderator, of a specific reviewType (if provided) // The map stores the moderator's newest review for each piece of content // The function will omit reviews whose newest verdict is "None" // The function will return error if any review is not created by moderator // The function does not verify the reviews //Outputs: // -map[string][]byte: Reviewed Hash (bytes encoded as string) -> Newest Review bytes // -error func GetNewestModeratorReviewsMapFromReviewsList(inputReviewsList []ReviewWithHash, moderatorIdentityHash [16]byte, networkType byte, reviewTypeProvided bool, reviewType string)(map[string][]byte, error){ isValid, err := identity.VerifyIdentityHash(moderatorIdentityHash, true, "Moderator") if (err != nil) { return nil, err } if (isValid == false){ moderatorIdentityHashHex := encoding.EncodeBytesToHexString(moderatorIdentityHash[:]) return nil, errors.New("GetNewestModeratorReviewsMapFromReviewsList called with invalid moderatorIdentityHash: " + moderatorIdentityHashHex) } isValid = helpers.VerifyNetworkType(networkType) if (isValid == false){ networkTypeString := helpers.ConvertByteToString(networkType) return nil, errors.New("GetNewestModeratorReviewsMapFromReviewsList called with invalid networkType: " + networkTypeString) } if (reviewTypeProvided == true){ if (reviewType != "Identity" && reviewType != "Profile" && reviewType != "Attribute" && reviewType != "Message"){ return nil, errors.New("GetNewestModeratorReviewsMapFromReviewsList called with invalid reviewType: " + reviewType) } } type ReviewInfoObject struct{ ReviewHash [29]byte Verdict string BroadcastTime int64 ReviewBytes []byte } // This map stores the newest review info for each reviewedHash // Map Structure: Reviewed Hash -> Review info object of newest review newestReviewInfoObjectsMap := make(map[string]ReviewInfoObject) for _, reviewWithHash := range inputReviewsList{ reviewHash := reviewWithHash.ReviewHash reviewBytes := reviewWithHash.ReviewBytes ableToRead, _, reviewNetworkType, reviewAuthor, reviewBroadcastTime, currentReviewType, reviewedHash, reviewVerdict, _, err := ReadReview(false, reviewBytes) if (err != nil) { return nil, err } if (ableToRead == false){ return nil, errors.New("GetNewestModeratorReviewsMapFromReviewsList called with invalid review") } if (reviewNetworkType != networkType){ continue } if (reviewAuthor != moderatorIdentityHash){ return nil, errors.New("GetNewestModeratorReviewsMapFromReviewsList receiving review by different reviewer") } if (reviewTypeProvided == true && currentReviewType != reviewType){ continue } existingNewestReviewInfoObject, exists := newestReviewInfoObjectsMap[string(reviewedHash)] if (exists == true){ existingBroadcastTime := existingNewestReviewInfoObject.BroadcastTime if (exists == false){ return nil, errors.New("reviewBroadcastTimesMap missing reviewHash entry") } if (existingBroadcastTime == reviewBroadcastTime){ // The reviewer must be malicious, or their computer clock was skewed somehow // We compare the review hashes to see which review to use // This is necessary so that a moderator's newest review will always be calculated the same way by all Seekia clients. existingNewestReviewHash := existingNewestReviewInfoObject.ReviewHash compareValue := bytes.Compare(reviewHash[:], existingNewestReviewHash[:]) if (compareValue == 0){ // This should not happen, because this function should be called with a list of unique reviews return nil, errors.New("GetNewestModeratorReviewsMapFromReviewsList called with reviewsList containing duplicate review.") } if (compareValue == -1){ continue } } else if (existingBroadcastTime > reviewBroadcastTime){ // The user has replaced their review with a newer one continue } } newNewestReviewInfoObject := ReviewInfoObject{ ReviewHash: reviewHash, Verdict: reviewVerdict, BroadcastTime: reviewBroadcastTime, ReviewBytes: reviewBytes, } newestReviewInfoObjectsMap[string(reviewedHash)] = newNewestReviewInfoObject } //Map Structure: Reviewed Hash -> Newest Review bytes newestReviewsMap := make(map[string][]byte) for reviewedHash, reviewInfoObject := range newestReviewInfoObjectsMap{ currentVerdict := reviewInfoObject.Verdict if (currentVerdict == "None"){ continue } reviewBytes := reviewInfoObject.ReviewBytes newestReviewsMap[reviewedHash] = reviewBytes } return newestReviewsMap, nil }