/// <summary>
        ///     Decodes the <seealso cref="ChecksumTable" /> in the specified
        ///     <seealso cref="ByteBuffer" /> and decrypts the final whirlpool hash.
        /// </summary>
        /// <param name="buffer"> The <seealso cref="ByteBuffer" /> containing the table. </param>
        /// <param name="whirlpool"> If whirlpool digests should be read. </param>
        /// <param name="modulus"> The modulus. </param>
        /// <param name="publicKey"> The public key. </param>
        /// <returns> The decoded <seealso cref="ChecksumTable" />. </returns>
        /// <exception cref="IOException"> if an I/O error occurs. </exception>
        public static ChecksumTable Decode(ByteBuffer buffer, bool whirlpool, BigInteger modulus, BigInteger publicKey)
        {
            /* find out how many entries there are and allocate a new table */
            var size  = whirlpool ? (buffer.limit() / 8) : (buffer.get() & 0xFF);
            var table = new ChecksumTable(size);

            /* calculate the whirlpool digest we expect to have at the end */
            byte[] masterDigest = null;
            if (whirlpool)
            {
                var temp = new byte[size * 72 + 1];
                buffer.position(0);
                buffer.get(temp);
                masterDigest = Whirlpool.Crypt(temp, 0, temp.Length);
            }

            /* read the entries */
            buffer.position(1);
            for (var i = 0; i < size; i++)
            {
                var crc     = buffer.getInt();
                var version = buffer.getInt();
                var digest  = new byte[64];
                if (whirlpool)
                {
                    buffer.get(digest);
                }
                table.entries[i] = new Entry(crc, version, digest);
            }

            /* read the trailing digest and check if it matches up */
            if (whirlpool)
            {
                var bytes = new byte[buffer.remaining()];
                buffer.get(bytes);
                var temp = ByteBuffer.wrap(bytes);

                if (modulus != null && publicKey != null)
                {
                    temp = Rsa.Crypt(buffer, modulus, publicKey);
                }

                if (temp.limit() != 66)
                {
                    throw new IOException("Decrypted data is not 66 bytes long");
                }

                for (var i = 0; i < 64; i++)
                {
                    if (temp.get(i + 1) != masterDigest[i])
                    {
                        throw new IOException("Whirlpool digest mismatch");
                    }
                }
            }

            /* if it looks good return the table */
            return(table);
        }
        /// <summary>
        ///     Encodes this <seealso cref="ChecksumTable" /> and encrypts the final whirlpool hash.
        /// </summary>
        /// <param name="whirlpool"> If whirlpool digests should be encoded. </param>
        /// <param name="modulus"> The modulus. </param>
        /// <param name="privateKey"> The private key. </param>
        /// <returns> The encoded <seealso cref="ByteBuffer" />. </returns>
        /// <exception cref="IOException"> if an I/O error occurs. </exception>
        public virtual ByteBuffer Encode(bool whirlpool, BigInteger modulus, BigInteger privateKey)
        {
            var bout = new ByteArrayOutputStream();
            var os   = new DataOutputStream(bout);

            try
            {
                /* as the new whirlpool format is more complicated we must write the number of entries */
                if (whirlpool)
                {
                    os.write(entries.Length);
                }

                /* encode the individual entries */
                for (var i = 0; i < entries.Length; i++)
                {
                    var entry = entries[i];
                    os.writeInt(entry.GetCrc());
                    os.writeInt(entry.GetVersion());
                    if (whirlpool)
                    {
                        os.write(entry.GetWhirlpool());
                    }
                }

                /* compute (and encrypt) the digest of the whole table */
                byte[] bytes;
                if (whirlpool)
                {
                    bytes = bout.toByteArray();
                    var temp = ByteBuffer.allocate(66);
                    temp.put(0);
                    temp.put(Whirlpool.Crypt(bytes, 5, bytes.Length - 5));
                    temp.put(0);
                    temp.flip();

                    if (modulus != null && privateKey != null)
                    {
                        temp = Rsa.Crypt(temp, modulus, privateKey);
                    }

                    bytes = new byte[temp.limit()];
                    temp.get(bytes);
                    os.write(bytes);
                }

                bytes = bout.toByteArray();
                return(ByteBuffer.wrap(bytes));
            }
            finally
            {
                os.close();
            }
        }