private void HashObject(JObject obj, JObject salts = null)
        {
            byte[][] hashList = new byte[obj.Count][];
            int      i        = 0;

            foreach ((string key, JToken value) in obj)
            {
                if (!salts.IsNullOrEmpty() && !salts.ContainsKey(key))
                {
                    IDictionary additionalExceptionData = new Dictionary <string, object>
                    {
                        { "missingKey", key }
                    };

                    throw new BadRequestException(
                              "The provided JSON defines an object which is different from the Salts object. Please check the JSON or the salt data.",
                              additionalExceptionData);
                }

                ObjectHashImplementation jKeyHash = new ObjectHashImplementation();
                jKeyHash.HashString(key);
                // TODO: check if acceptable
                // object keys are not salted, i don't see a big issue alternative would be jKeyHash.HashString(SALT + o.Key); but its quite difficult for an user to store the salts for object keys

                ObjectHashImplementation jValHash = new ObjectHashImplementation();
                jValHash.HashJToken(value, salts.IsNullOrEmpty() ? null : salts[key]);

                // merge both hashes (of key and value)
                hashList[i] = jKeyHash.Hash.Concat(jValHash.Hash).ToArray();
                i++;
            }

            // objects should always be sorted
            HashListOfHashes(hashList, 'd', true);
        }
        private void AddTaggedByteArray(char tag, byte[] byteArray, JToken salt = null)
        {
            // copying of byteArrays is quite ugly but there is no nicer way in C# to join two byte arrays
            byte[] merged = new byte[byteArray.Length + 1];
            byteArray.CopyTo(merged, 1);
            merged[0] = (byte)tag;
            byte[] tempHash = _digester.ComputeHash(merged);

            if (salt != null)
            {
                // validate the salt is hex and block size long
                HexConverter.ValidateStringIsHexAndBlockLength(salt);
                // hash salt to equally distribute randomness
                ObjectHashImplementation jKeyHash = new ObjectHashImplementation();
                jKeyHash.HashString((string)salt);
                // merge salt and object hash as list
                byte[][] hashList = new byte[2][];
                hashList[0] = jKeyHash.Hash;
                hashList[1] = tempHash;

                HashListOfHashes(hashList, 'l');
            }
            else
            {
                Hash = tempHash;
            }
        }
        private static (JToken json, JToken salts) RecursiveRedactDataAndSalts(JToken json, JToken redactSettings,
                                                                               JToken salts = null)
        {
            // ReSharper disable once SwitchStatementMissingSomeCases
            switch (redactSettings.Type)
            {
            case JTokenType.Boolean:
                if (!(bool)redactSettings)
                {
                    return(json, salts);
                }

                ObjectHashImplementation objectHash = new ObjectHashImplementation();
                objectHash.HashJToken(json, salts);
                return("**REDACTED**" + objectHash.HashAsString(), "**REDACTED**");

            case JTokenType.Object:
                try
                {
                    return(RedactObject((JObject)json, (JObject)redactSettings,
                                        salts.IsNullOrEmpty() ? null : (JObject)salts));
                }
                catch (InvalidCastException e)
                {
                    throw new BadRequestException(
                              "The provided JSON does not contain an object -> {} where the redact settings require one. Please check the JSON data or the redact settings.", e);
                }

            case JTokenType.Array:
                try
                {
                    return(RedactArray((JArray)json, (JArray)redactSettings,
                                       salts.IsNullOrEmpty() ? null : (JArray)salts));
                }
                catch (InvalidCastException e)
                {
                    throw new BadRequestException(
                              "The provided JSON does not contain an array -> [] where the redact settings require one. Please check the JSON data or the redact settings", e);
                }

            case JTokenType.Null:
            {
                return(json, salts);
            }

            default:
                throw new BadRequestException(
                          "The redact setting JSON is invalid. It can only contain a nested JSON, arrays and the data type Boolean.");
            }
        }
        private void HashArray(JArray array, JArray salts = null)
        {
            if (!salts.IsNullOrEmpty() && salts.Count != array.Count)
            {
                throw new BadRequestException(
                          "The corresponding JSON object contains an array that is different in size from the Salts array. They need to be equally long.");
            }

            byte[][] hashList = new byte[array.Count][];
            for (int i = 0; i < array.Count; i++)
            {
                ObjectHashImplementation aElementHash = new ObjectHashImplementation();
                aElementHash.HashJToken(array[i], salts.IsNullOrEmpty() ? null : salts[i]);
                hashList[i] = aElementHash.Hash;
            }

            // sorting arrays can be needed, but the default should be not to sort arrays
            HashListOfHashes(hashList, 'l', Globals.SORT_ARRAY);
        }
 // ReSharper disable once UnusedMember.Global
 public int CompareTo(ObjectHashImplementation other)
 {
     return(string.Compare(HashAsString(), other.HashAsString(), Globals.STRING_COMPARE_METHOD));
 }