181 lines
7.1 KiB
Python
181 lines
7.1 KiB
Python
#!/usr/bin/env python3.7
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
TFC - Onion-routed, endpoint secure messaging system
|
|
Copyright (C) 2013-2020 Markus Ottela
|
|
|
|
This file is part of TFC.
|
|
|
|
TFC is free software: you can redistribute it and/or modify it under the terms
|
|
of the GNU General Public License as published by the Free Software Foundation,
|
|
either version 3 of the License, or (at your option) any later version.
|
|
|
|
TFC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
PURPOSE. See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with TFC. If not, see <https://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
import base64
|
|
import os
|
|
import unittest
|
|
|
|
from datetime import datetime
|
|
|
|
from src.common.encoding import (b58encode, bool_to_bytes, double_to_bytes, str_to_bytes, int_to_bytes,
|
|
b58decode, bytes_to_bool, bytes_to_double, bytes_to_str, bytes_to_int,
|
|
onion_address_to_pub_key, unicode_padding, pub_key_to_short_address, b85encode,
|
|
pub_key_to_onion_address, rm_padding_str, bytes_to_timestamp, b10encode)
|
|
from src.common.statics import (ENCODED_BOOLEAN_LENGTH, ENCODED_FLOAT_LENGTH, ENCODED_INTEGER_LENGTH,
|
|
FINGERPRINT_LENGTH, ONION_SERVICE_PUBLIC_KEY_LENGTH, PADDED_UTF32_STR_LENGTH,
|
|
PADDING_LENGTH, SYMMETRIC_KEY_LENGTH, TFC_PUBLIC_KEY_LENGTH, TRUNC_ADDRESS_LENGTH)
|
|
|
|
|
|
class TestBase58EncodeAndDecode(unittest.TestCase):
|
|
|
|
def setUp(self) -> None:
|
|
"""Pre-test actions."""
|
|
self.key = SYMMETRIC_KEY_LENGTH * b'\x01'
|
|
|
|
def test_encoding_and_decoding_of_random_local_keys(self) -> None:
|
|
for _ in range(100):
|
|
key = os.urandom(SYMMETRIC_KEY_LENGTH)
|
|
encoded = b58encode(key)
|
|
decoded = b58decode(encoded)
|
|
self.assertEqual(key, decoded)
|
|
|
|
def test_encoding_and_decoding_of_random_public_keys(self) -> None:
|
|
for _ in range(100):
|
|
key = os.urandom(TFC_PUBLIC_KEY_LENGTH)
|
|
encoded = b58encode(key, public_key=True)
|
|
decoded = b58decode(encoded, public_key=True)
|
|
self.assertEqual(key, decoded)
|
|
|
|
def test_invalid_decoding(self) -> None:
|
|
encoded = b58encode(self.key) # 5HpjE2Hs7vjU4SN3YyPQCdhzCu92WoEeuE6PWNuiPyTu3ESGnzn
|
|
changed = encoded[:-1] + 'a'
|
|
with self.assertRaises(ValueError):
|
|
b58decode(changed)
|
|
|
|
def test_public_keys_raise_value_error_when_expecting_local_key(self) -> None:
|
|
b58_pub_key = b58encode(self.key)
|
|
with self.assertRaises(ValueError):
|
|
b58decode(b58_pub_key, public_key=True)
|
|
|
|
def test_local_keys_raise_value_error_when_expecting_public_key(self) -> None:
|
|
b58_file_key = b58encode(self.key, public_key=True)
|
|
with self.assertRaises(ValueError):
|
|
b58decode(b58_file_key)
|
|
|
|
def test_bitcoin_wif_test_vectors(self) -> None:
|
|
"""Test vectors are available at
|
|
https://en.bitcoin.it/wiki/Wallet_import_format
|
|
"""
|
|
byte_key = bytes.fromhex("0C28FCA386C7A227600B2FE50B7CAE11"
|
|
"EC86D3BF1FBE471BE89827E19D72AA1D")
|
|
|
|
b58_key = "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ"
|
|
|
|
self.assertEqual(b58encode(byte_key), b58_key)
|
|
self.assertEqual(b58decode(b58_key), byte_key)
|
|
|
|
|
|
class TestBase85Encode(unittest.TestCase):
|
|
|
|
def test_b85encode(self) -> None:
|
|
message = os.urandom(100)
|
|
self.assertEqual(b85encode(message),
|
|
base64.b85encode(message).decode())
|
|
|
|
|
|
class TestBase10Encode(unittest.TestCase):
|
|
|
|
def test_b10encode(self) -> None:
|
|
self.assertEqual(b10encode(FINGERPRINT_LENGTH * b'a'),
|
|
'44046402572626160612103472728795008085361523578694645928734845681441465000289')
|
|
|
|
|
|
class TestUnicodePadding(unittest.TestCase):
|
|
|
|
def test_padding(self) -> None:
|
|
for s in range(0, PADDING_LENGTH):
|
|
string = s * 'm'
|
|
padded = unicode_padding(string)
|
|
self.assertEqual(len(padded), PADDING_LENGTH)
|
|
|
|
# Verify removal of padding doesn't alter the string
|
|
self.assertEqual(string, padded[:-ord(padded[-1:])])
|
|
|
|
def test_oversize_msg_raises_critical_error(self) -> None:
|
|
for s in range(PADDING_LENGTH, PADDING_LENGTH+1):
|
|
with self.assertRaises(SystemExit):
|
|
unicode_padding(s * 'm')
|
|
|
|
|
|
class TestRmPaddingStr(unittest.TestCase):
|
|
|
|
def test_padding_removal(self) -> None:
|
|
for i in range(0, 1000):
|
|
string = i * 'm'
|
|
length = PADDING_LENGTH - (len(string) % PADDING_LENGTH)
|
|
padded = string + length * chr(length)
|
|
self.assertEqual(rm_padding_str(padded), string)
|
|
|
|
|
|
class TestConversions(unittest.TestCase):
|
|
|
|
def test_conversion_back_and_forth(self) -> None:
|
|
pub_key = os.urandom(SYMMETRIC_KEY_LENGTH)
|
|
self.assertEqual(onion_address_to_pub_key(pub_key_to_onion_address(pub_key)), pub_key)
|
|
|
|
def test_pub_key_to_short_addr(self) -> None:
|
|
self.assertEqual(len(pub_key_to_short_address(bytes(ONION_SERVICE_PUBLIC_KEY_LENGTH))),
|
|
TRUNC_ADDRESS_LENGTH)
|
|
|
|
self.assertIsInstance(pub_key_to_short_address(bytes(ONION_SERVICE_PUBLIC_KEY_LENGTH)), str)
|
|
|
|
def test_bool_to_bytes(self) -> None:
|
|
self.assertEqual( bool_to_bytes(False), b'\x00')
|
|
self.assertEqual( bool_to_bytes(True), b'\x01')
|
|
self.assertEqual(len(bool_to_bytes(True)), ENCODED_BOOLEAN_LENGTH)
|
|
|
|
def test_bytes_to_bool(self) -> None:
|
|
self.assertEqual(bytes_to_bool(b'\x00'), False)
|
|
self.assertEqual(bytes_to_bool(b'\x01'), True)
|
|
|
|
def test_int_to_bytes(self) -> None:
|
|
self.assertEqual( int_to_bytes(1), b'\x00\x00\x00\x00\x00\x00\x00\x01')
|
|
self.assertEqual(len(int_to_bytes(1)), ENCODED_INTEGER_LENGTH)
|
|
|
|
def test_bytes_to_int(self) -> None:
|
|
self.assertEqual(bytes_to_int(b'\x00\x00\x00\x00\x00\x00\x00\x01'), 1)
|
|
|
|
def test_double_to_bytes(self) -> None:
|
|
self.assertEqual( double_to_bytes(1.0), bytes.fromhex('000000000000f03f'))
|
|
self.assertEqual( double_to_bytes(1.1), bytes.fromhex('9a9999999999f13f'))
|
|
self.assertEqual(len(double_to_bytes(1.1)), ENCODED_FLOAT_LENGTH)
|
|
|
|
def test_bytes_to_double(self) -> None:
|
|
self.assertEqual(bytes_to_double(bytes.fromhex('000000000000f03f')), 1.0)
|
|
self.assertEqual(bytes_to_double(bytes.fromhex('9a9999999999f13f')), 1.1)
|
|
|
|
def test_str_to_bytes(self) -> None:
|
|
encoded = str_to_bytes('test')
|
|
self.assertIsInstance(encoded, bytes)
|
|
self.assertEqual(len(encoded), PADDED_UTF32_STR_LENGTH)
|
|
|
|
def test_bytes_to_str(self) -> None:
|
|
encoded = str_to_bytes('test')
|
|
self.assertEqual(bytes_to_str(encoded), 'test')
|
|
|
|
def test_bytes_to_timestamp(self) -> None:
|
|
encoded = bytes.fromhex('00000000')
|
|
self.assertIsInstance(bytes_to_timestamp(encoded), datetime)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main(exit=False)
|