// Copyright (c) 2014-2018, The Monero Project
// 
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
// 
// 1. Redistributions of source code must retain the above copyright notice, this list of
//    conditions and the following disclaimer.
// 
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
//    of conditions and the following disclaimer in the documentation and/or other
//    materials provided with the distribution.
// 
// 3. Neither the name of the copyright holder nor the names of its contributors may be
//    used to endorse or promote products derived from this software without specific
//    prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// 
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers

#include <assert.h>
#include <string.h>
#include <stdbool.h>
#include <sys/types.h>
#include "base58.h"
#include "int-util.h"
#include "sha2.h"
#include "../base58.h"

const size_t alphabet_size = 58; // sizeof(b58digits_ordered) - 1;
const size_t encoded_block_sizes[] = {0, 2, 3, 5, 6, 7, 9, 10, 11};
const size_t full_block_size = sizeof(encoded_block_sizes) / sizeof(encoded_block_sizes[0]) - 1;
const size_t full_encoded_block_size = 11; // encoded_block_sizes[full_block_size];
const size_t addr_checksum_size = 4;
const int decoded_block_sizes[] = {0, -1, 1, 2, -1, 3, 4, 5, -1, 6, 7, 8};
#define reverse_alphabet(letter) ((int8_t) b58digits_map[(int)letter])


uint64_t uint_8be_to_64(const uint8_t* data, size_t size)
{
	assert(1 <= size && size <= sizeof(uint64_t));

	uint64_t res = 0;
	switch (9 - size)
	{
	case 1:            res |= *data++; /* FALLTHRU */
	case 2: res <<= 8; res |= *data++; /* FALLTHRU */
	case 3: res <<= 8; res |= *data++; /* FALLTHRU */
	case 4: res <<= 8; res |= *data++; /* FALLTHRU */
	case 5: res <<= 8; res |= *data++; /* FALLTHRU */
	case 6: res <<= 8; res |= *data++; /* FALLTHRU */
	case 7: res <<= 8; res |= *data++; /* FALLTHRU */
	case 8: res <<= 8; res |= *data; break;
	default: assert(false);
	}

	return res;
}

void uint_64_to_8be(uint64_t num, size_t size, uint8_t* data)
{
	assert(1 <= size && size <= sizeof(uint64_t));

	uint64_t num_be = SWAP64(num);
	memcpy(data, (uint8_t*)(&num_be) + sizeof(uint64_t) - size, size);
}

void encode_block(const char* block, size_t size, char* res)
{
	assert(1 <= size && size <= full_block_size);

	uint64_t num = uint_8be_to_64((uint8_t*)(block), size);
	int i = ((int)(encoded_block_sizes[size])) - 1;
	while (0 <= i)
	{
		uint64_t remainder = num % alphabet_size;
		num /= alphabet_size;
		res[i] = b58digits_ordered[remainder];
		--i;
	}
}

bool decode_block(const char* block, size_t size, char* res)
{
	assert(1 <= size && size <= full_encoded_block_size);

	int res_size = decoded_block_sizes[size];
	if (res_size <= 0)
		return false; // Invalid block size

	uint64_t res_num = 0;
	uint64_t order = 1;
	for (size_t i = size - 1; i < size; --i)
	{
		int digit = reverse_alphabet(block[i]);
		if (digit < 0)
			return false; // Invalid symbol

		uint64_t product_hi;
		uint64_t tmp = res_num + mul128(order, (uint64_t) digit, &product_hi);
		if (tmp < res_num || 0 != product_hi)
			return false; // Overflow

		res_num = tmp;
		order *= alphabet_size; // Never overflows, 58^10 < 2^64
	}

	if ((size_t)res_size < full_block_size && (UINT64_C(1) << (8 * res_size)) <= res_num)
		return false; // Overflow

	uint_64_to_8be(res_num, res_size, (uint8_t*)(res));

	return true;
}


bool xmr_base58_encode(char *b58, size_t *b58sz, const void *data, size_t binsz)
{
	if (binsz==0)
		return true;

	const char * data_bin = data;
	size_t full_block_count = binsz / full_block_size;
	size_t last_block_size = binsz % full_block_size;
	size_t res_size = full_block_count * full_encoded_block_size + encoded_block_sizes[last_block_size];

	if (b58sz){
		if (res_size >= *b58sz){
			return false;
		}
		*b58sz = res_size;
	}

	for (size_t i = 0; i < full_block_count; ++i)
	{
		encode_block(data_bin + i * full_block_size, full_block_size, b58 + i * full_encoded_block_size);
	}

	if (0 < last_block_size)
	{
		encode_block(data_bin + full_block_count * full_block_size, last_block_size, b58 + full_block_count * full_encoded_block_size);
	}

	return true;
}

bool xmr_base58_decode(const char *b58, size_t b58sz, void *data, size_t *binsz)
{
	if (b58sz == 0) {
		*binsz = 0;
		return true;
	}

	size_t full_block_count = b58sz / full_encoded_block_size;
	size_t last_block_size = b58sz % full_encoded_block_size;
	int last_block_decoded_size = decoded_block_sizes[last_block_size];
	if (last_block_decoded_size < 0) {
		*binsz = 0;
		return false; // Invalid enc length
	}

	size_t data_size = full_block_count * full_block_size + last_block_decoded_size;
	if (*binsz < data_size){
		*binsz = 0;
		return false;
	}

	char * data_bin = data;
	for (size_t i = 0; i < full_block_count; ++i)
	{
		if (!decode_block(b58 + i * full_encoded_block_size, full_encoded_block_size, data_bin + i * full_block_size))
			return false;
	}

	if (0 < last_block_size)
	{
		if (!decode_block(b58 + full_block_count * full_encoded_block_size, last_block_size,
											data_bin + full_block_count * full_block_size))
			return false;
	}

	return true;
}

int xmr_base58_addr_encode_check(uint64_t tag, const uint8_t *data, size_t binsz, char *b58, size_t b58sz)
{
	if (binsz > 128 || tag > 127) {  // tag varint
		return false;
	}

	size_t b58size = b58sz;
	uint8_t buf[binsz + 1 + HASHER_DIGEST_LENGTH];
	uint8_t *hash = buf + binsz + 1;
	buf[0] = (uint8_t) tag;
	memcpy(buf + 1, data, binsz);
	hasher_Raw(HASHER_SHA3K, buf, binsz + 1, hash);

	bool r = xmr_base58_encode(b58, &b58size, buf, binsz + 1 + addr_checksum_size);
	return (int) (!r ? 0 : b58size);
}

int xmr_base58_addr_decode_check(const char *addr, size_t sz, uint64_t *tag, void *data, size_t datalen)
{
	size_t buflen = 1 + 64 + addr_checksum_size;
	uint8_t buf[buflen];
	uint8_t hash[HASHER_DIGEST_LENGTH];

	if (!xmr_base58_decode(addr, sz, buf, &buflen)){
		return 0;
	}

	size_t res_size = buflen - addr_checksum_size - 1;
	if (datalen < res_size){
		return 0;
	}

	if (buflen <= addr_checksum_size+1) {
		return 0;
	}

	hasher_Raw(HASHER_SHA3K, buf, buflen - addr_checksum_size, hash);
	if (memcmp(hash, buf + buflen - addr_checksum_size, addr_checksum_size) != 0){
		return 0;
	}

	*tag = buf[0];
	if (*tag > 127){
		return false; // varint
	}

	memcpy(data, buf+1, res_size);
	return (int) res_size;
}
