seekia/internal/moderation/readReviews/readReviews.go

984 lines
35 KiB
Go

// 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
}