diff --git a/Makefile b/Makefile index 85619b56..7020159a 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,9 @@ clean_stmhal: ## clean stmhal build clean_unix: ## clean unix build make -C vendor/micropython/unix clean +test: ## run unit tests + cd src/tests ; ./run_tests.sh + flash: ## flash firmware using st-flash st-flash write $(STMHAL_BUILD_DIR)/firmware0.bin 0x8000000 sleep 0.1 diff --git a/extmod/modTrezorCrypto/modTrezorCrypto-sha512.h b/extmod/modTrezorCrypto/modTrezorCrypto-sha512.h new file mode 100644 index 00000000..11f61cb9 --- /dev/null +++ b/extmod/modTrezorCrypto/modTrezorCrypto-sha512.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) Pavol Rusnak, SatoshiLabs + * + * Licensed under Microsoft Reference Source License (Ms-RSL) + * see LICENSE.md file for details + */ + +#include "trezor-crypto/sha2.h" + +// class Sha512(object): +typedef struct _mp_obj_Sha512_t { + mp_obj_base_t base; +} mp_obj_Sha512_t; + +// def Sha512.__init__(self): +STATIC mp_obj_t mod_TrezorCrypto_Sha512_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_obj_Sha512_t *o = m_new_obj(mp_obj_Sha512_t); + o->base.type = type; + return MP_OBJ_FROM_PTR(o); +} + +// def Sha512.hash(self, data: bytes) -> bytes +STATIC mp_obj_t mod_TrezorCrypto_Sha512_hash(mp_obj_t self, mp_obj_t data) { + mp_buffer_info_t databuf; + mp_get_buffer_raise(data, &databuf, MP_BUFFER_READ); + vstr_t vstr; + vstr_init_len(&vstr, 64); // 512 bit = 64 bytes + sha512_Raw(databuf.buf, databuf.len, (uint8_t *)vstr.buf); + return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_TrezorCrypto_Sha512_hash_obj, mod_TrezorCrypto_Sha512_hash); + +// Sha512 stuff + +STATIC const mp_rom_map_elem_t mod_TrezorCrypto_Sha512_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_hash), MP_ROM_PTR(&mod_TrezorCrypto_Sha512_hash_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(mod_TrezorCrypto_Sha512_locals_dict, mod_TrezorCrypto_Sha512_locals_dict_table); + +STATIC const mp_obj_type_t mod_TrezorCrypto_Sha512_type = { + { &mp_type_type }, + .name = MP_QSTR_Sha512, + .make_new = mod_TrezorCrypto_Sha512_make_new, + .locals_dict = (void*)&mod_TrezorCrypto_Sha512_locals_dict, +}; diff --git a/extmod/modTrezorCrypto/modTrezorCrypto.c b/extmod/modTrezorCrypto/modTrezorCrypto.c index e5ab0f82..baa7ba57 100644 --- a/extmod/modTrezorCrypto/modTrezorCrypto.c +++ b/extmod/modTrezorCrypto/modTrezorCrypto.c @@ -17,6 +17,7 @@ #include "modTrezorCrypto-base58.h" #include "modTrezorCrypto-sha256.h" +#include "modTrezorCrypto-sha512.h" // module stuff @@ -24,6 +25,7 @@ STATIC const mp_rom_map_elem_t mp_module_TrezorCrypto_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_TrezorCrypto) }, { MP_ROM_QSTR(MP_QSTR_Base58), MP_ROM_PTR(&mod_TrezorCrypto_Base58_type) }, { MP_ROM_QSTR(MP_QSTR_Sha256), MP_ROM_PTR(&mod_TrezorCrypto_Sha256_type) }, + { MP_ROM_QSTR(MP_QSTR_Sha512), MP_ROM_PTR(&mod_TrezorCrypto_Sha512_type) }, }; STATIC MP_DEFINE_CONST_DICT(mp_module_TrezorCrypto_globals, mp_module_TrezorCrypto_globals_table); diff --git a/src/lib/unittest.py b/src/lib/unittest.py new file mode 100644 index 00000000..8cb8bfbb --- /dev/null +++ b/src/lib/unittest.py @@ -0,0 +1,203 @@ +class SkipTest(Exception): + pass + + +class AssertRaisesContext: + + def __init__(self, exc): + self.expected = exc + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + if exc_type is None: + assert False, "%r not raised" % self.expected + if issubclass(exc_type, self.expected): + return True + return False + + +class TestCase: + + def fail(self, msg=''): + assert False, msg + + def assertEqual(self, x, y, msg=''): + if not msg: + msg = "%r vs (expected) %r" % (x, y) + assert x == y, msg + + def assertNotEqual(self, x, y, msg=''): + if not msg: + msg = "%r not expected to be equal %r" % (x, y) + assert x != y, msg + + def assertAlmostEqual(self, x, y, places=None, msg='', delta=None): + if x == y: + return + if delta is not None and places is not None: + raise TypeError("specify delta or places not both") + + if delta is not None: + if abs(x - y) <= delta: + return + if not msg: + msg = '%r != %r within %r delta' % (x, y, delta) + else: + if places is None: + places = 7 + if round(abs(y-x), places) == 0: + return + if not msg: + msg = '%r != %r within %r places' % (x, y, places) + + assert False, msg + + def assertNotAlmostEqual(self, x, y, places=None, msg='', delta=None): + if delta is not None and places is not None: + raise TypeError("specify delta or places not both") + + if delta is not None: + if not (x == y) and abs(x - y) > delta: + return + if not msg: + msg = '%r == %r within %r delta' % (x, y, delta) + else: + if places is None: + places = 7 + if not (x == y) and round(abs(y-x), places) != 0: + return + if not msg: + msg = '%r == %r within %r places' % (x, y, places) + + assert False, msg + + def assertIs(self, x, y, msg=''): + if not msg: + msg = "%r is not %r" % (x, y) + assert x is y, msg + + def assertIsNot(self, x, y, msg=''): + if not msg: + msg = "%r is %r" % (x, y) + assert x is not y, msg + + def assertIsNone(self, x, msg=''): + if not msg: + msg = "%r is not None" % x + assert x is None, msg + + def assertIsNotNone(self, x, msg=''): + if not msg: + msg = "%r is None" % x + assert x is not None, msg + + def assertTrue(self, x, msg=''): + if not msg: + msg = "Expected %r to be True" % x + assert x, msg + + def assertFalse(self, x, msg=''): + if not msg: + msg = "Expected %r to be False" % x + assert not x, msg + + def assertIn(self, x, y, msg=''): + if not msg: + msg = "Expected %r to be in %r" % (x, y) + assert x in y, msg + + def assertIsInstance(self, x, y, msg=''): + assert isinstance(x, y), msg + + def assertRaises(self, exc, func=None, *args, **kwargs): + if func is None: + return AssertRaisesContext(exc) + + try: + func(*args, **kwargs) + assert False, "%r not raised" % exc + except Exception as e: + if isinstance(e, exc): + return + raise + + + +def skip(msg): + def _decor(fun): + # We just replace original fun with _inner + def _inner(self): + raise SkipTest(msg) + return _inner + return _decor + + +def skipUnless(cond, msg): + if cond: + return lambda x: x + return skip(msg) + + +class TestSuite: + def __init__(self): + self.tests = [] + def addTest(self, cls): + self.tests.append(cls) + +class TestRunner: + def run(self, suite): + res = TestResult() + for c in suite.tests: + run_class(c, res) + return res + +class TestResult: + def __init__(self): + self.errorsNum = 0 + self.failuresNum = 0 + self.skippedNum = 0 + self.testsRun = 0 + + def wasSuccessful(self): + return self.errorsNum == 0 and self.failuresNum == 0 + +# TODO: Uncompliant +def run_class(c, test_result): + o = c() + set_up = getattr(o, "setUp", lambda: None) + tear_down = getattr(o, "tearDown", lambda: None) + print('class', c.__qualname__) + for name in dir(o): + if name.startswith("test"): + print(name, end=' ...') + m = getattr(o, name) + try: + set_up() + test_result.testsRun += 1 + m() + tear_down() + print(" ok") + except SkipTest as e: + print(" skipped:", e.args[0]) + test_result.skippedNum += 1 + + +def main(module="__main__"): + def test_cases(m): + for tn in dir(m): + c = getattr(m, tn) + if isinstance(c, object) and isinstance(c, type) and issubclass(c, TestCase): + yield c + + m = __import__(module) + suite = TestSuite() + for c in test_cases(m): + suite.addTest(c) + runner = TestRunner() + result = runner.run(suite) + msg = "Ran %d tests" % result.testsRun + if result.skippedNum > 0: + msg += " (%d skipped)" % result.skippedNum + print(msg) diff --git a/src/tests/run_tests.sh b/src/tests/run_tests.sh new file mode 100755 index 00000000..b6358c45 --- /dev/null +++ b/src/tests/run_tests.sh @@ -0,0 +1,5 @@ +#!/bin/sh +for i in *.py; do + echo + ../../vendor/micropython/unix/micropython $i +done diff --git a/src/tests/test_crypto_base58.py b/src/tests/test_crypto_base58.py new file mode 100644 index 00000000..a85c0a86 --- /dev/null +++ b/src/tests/test_crypto_base58.py @@ -0,0 +1,74 @@ +import sys +sys.path.append('..') +sys.path.append('../lib') +import unittest +import trezor.utils + +import trezor.crypto.base58 + +class TestCryptoBase58(unittest.TestCase): + + # vectors from https://github.com/bitcoin/bitcoin/blob/master/src/test/data/base58_keys_valid.json + vectors = [ + ('0065a16059864a2fdbc7c99a4723a8395bc6f188eb', '1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i'), + ('0574f209f6ea907e2ea48f74fae05782ae8a665257', '3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou'), + ('6f53c0307d6851aa0ce7825ba883c6bd9ad242b486', 'mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs'), + ('c46349a418fc4578d10a372b54b45c280cc8c4382f', '2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br'), + ('80eddbdc1168f1daeadbd3e44c1e3f8f5a284c2029f78ad26af98583a499de5b19', '5Kd3NBUAdUnhyzenEwVLy9pBKxSwXvE9FMPyR4UKZvpe6E3AgLr'), + ('8055c9bccb9ed68446d1b75273bbce89d7fe013a8acd1625514420fb2aca1a21c401', 'Kz6UJmQACJmLtaQj5A3JAge4kVTNQ8gbvXuwbmCj7bsaabudb3RD'), + ('ef36cb93b9ab1bdabf7fb9f2c04f1b9cc879933530ae7842398eef5a63a56800c2', '9213qJab2HNEpMpYNBa7wHGFKKbkDn24jpANDs2huN3yi4J11ko'), + ('efb9f4892c9e8282028fea1d2667c4dc5213564d41fc5783896a0d843fc15089f301', 'cTpB4YiyKiBcPxnefsDpbnDxFDffjqJob8wGCEDXxgQ7zQoMXJdH'), + ('006d23156cbbdcc82a5a47eee4c2c7c583c18b6bf4', '1Ax4gZtb7gAit2TivwejZHYtNNLT18PUXJ'), + ('05fcc5460dd6e2487c7d75b1963625da0e8f4c5975', '3QjYXhTkvuj8qPaXHTTWb5wjXhdsLAAWVy'), + ('6ff1d470f9b02370fdec2e6b708b08ac431bf7a5f7', 'n3ZddxzLvAY9o7184TB4c6FJasAybsw4HZ'), + ('c4c579342c2c4c9220205e2cdc285617040c924a0a', '2NBFNJTktNa7GZusGbDbGKRZTxdK9VVez3n'), + ('80a326b95ebae30164217d7a7f57d72ab2b54e3be64928a19da0210b9568d4015e', '5K494XZwps2bGyeL71pWid4noiSNA2cfCibrvRWqcHSptoFn7rc'), + ('807d998b45c219a1e38e99e7cbd312ef67f77a455a9b50c730c27f02c6f730dfb401', 'L1RrrnXkcKut5DEMwtDthjwRcTTwED36thyL1DebVrKuwvohjMNi'), + ('efd6bca256b5abc5602ec2e1c121a08b0da2556587430bcf7e1898af2224885203', '93DVKyFYwSN6wEo3E2fCrFPUp17FtrtNi2Lf7n4G3garFb16CRj'), + ('efa81ca4e8f90181ec4b61b6a7eb998af17b2cb04de8a03b504b9e34c4c61db7d901', 'cTDVKtMGVYWTHCb1AFjmVbEbWjvKpKqKgMaR3QJxToMSQAhmCeTN'), + ('007987ccaa53d02c8873487ef919677cd3db7a6912', '1C5bSj1iEGUgSTbziymG7Cn18ENQuT36vv'), + ('0563bcc565f9e68ee0189dd5cc67f1b0e5f02f45cb', '3AnNxabYGoTxYiTEZwFEnerUoeFXK2Zoks'), + ('6fef66444b5b17f14e8fae6e7e19b045a78c54fd79', 'n3LnJXCqbPjghuVs8ph9CYsAe4Sh4j97wk'), + ('c4c3e55fceceaa4391ed2a9677f4a4d34eacd021a0', '2NB72XtkjpnATMggui83aEtPawyyKvnbX2o'), + ('80e75d936d56377f432f404aabb406601f892fd49da90eb6ac558a733c93b47252', '5KaBW9vNtWNhc3ZEDyNCiXLPdVPHCikRxSBWwV9NrpLLa4LsXi9'), + ('808248bd0375f2f75d7e274ae544fb920f51784480866b102384190b1addfbaa5c01', 'L1axzbSyynNYA8mCAhzxkipKkfHtAXYF4YQnhSKcLV8YXA874fgT'), + ('ef44c4f6a096eac5238291a94cc24c01e3b19b8d8cef72874a079e00a242237a52', '927CnUkUbasYtDwYwVn2j8GdTuACNnKkjZ1rpZd2yBB1CLcnXpo'), + ('efd1de707020a9059d6d3abaf85e17967c6555151143db13dbb06db78df0f15c6901', 'cUcfCMRjiQf85YMzzQEk9d1s5A4K7xL5SmBCLrezqXFuTVefyhY7'), + ('00adc1cc2081a27206fae25792f28bbc55b831549d', '1Gqk4Tv79P91Cc1STQtU3s1W6277M2CVWu'), + ('05188f91a931947eddd7432d6e614387e32b244709', '33vt8ViH5jsr115AGkW6cEmEz9MpvJSwDk'), + ('6f1694f5bc1a7295b600f40018a618a6ea48eeb498', 'mhaMcBxNh5cqXm4aTQ6EcVbKtfL6LGyK2H'), + ('c43b9b3fd7a50d4f08d1a5b0f62f644fa7115ae2f3', '2MxgPqX1iThW3oZVk9KoFcE5M4JpiETssVN'), + ('80091035445ef105fa1bb125eccfb1882f3fe69592265956ade751fd095033d8d0', '5HtH6GdcwCJA4ggWEL1B3jzBBUB8HPiBi9SBc5h9i4Wk4PSeApR'), + ('80ab2b4bcdfc91d34dee0ae2a8c6b6668dadaeb3a88b9859743156f462325187af01', 'L2xSYmMeVo3Zek3ZTsv9xUrXVAmrWxJ8Ua4cw8pkfbQhcEFhkXT8'), + ('efb4204389cef18bbe2b353623cbf93e8678fbc92a475b664ae98ed594e6cf0856', '92xFEve1Z9N8Z641KQQS7ByCSb8kGjsDzw6fAmjHN1LZGKQXyMq'), + ('efe7b230133f1b5489843260236b06edca25f66adb1be455fbd38d4010d48faeef01', 'cVM65tdYu1YK37tNoAyGoJTR13VBYFva1vg9FLuPAsJijGvG6NEA'), + ('00c4c1b72491ede1eedaca00618407ee0b772cad0d', '1JwMWBVLtiqtscbaRHai4pqHokhFCbtoB4'), + ('05f6fe69bcb548a829cce4c57bf6fff8af3a5981f9', '3QCzvfL4ZRvmJFiWWBVwxfdaNBT8EtxB5y'), + ('6f261f83568a098a8638844bd7aeca039d5f2352c0', 'mizXiucXRCsEriQCHUkCqef9ph9qtPbZZ6'), + ('c4e930e1834a4d234702773951d627cce82fbb5d2e', '2NEWDzHWwY5ZZp8CQWbB7ouNMLqCia6YRda'), + ('80d1fab7ab7385ad26872237f1eb9789aa25cc986bacc695e07ac571d6cdac8bc0', '5KQmDryMNDcisTzRp3zEq9e4awRmJrEVU1j5vFRTKpRNYPqYrMg'), + ('80b0bbede33ef254e8376aceb1510253fc3550efd0fcf84dcd0c9998b288f166b301', 'L39Fy7AC2Hhj95gh3Yb2AU5YHh1mQSAHgpNixvm27poizcJyLtUi'), + ('ef037f4192c630f399d9271e26c575269b1d15be553ea1a7217f0cb8513cef41cb', '91cTVUcgydqyZLgaANpf1fvL55FH53QMm4BsnCADVNYuWuqdVys'), + ('ef6251e205e8ad508bab5596bee086ef16cd4b239e0cc0c5d7c4e6035441e7d5de01', 'cQspfSzsgLeiJGB2u8vrAiWpCU4MxUT6JseWo2SjXy4Qbzn2fwDw'), + ('005eadaf9bb7121f0f192561a5a62f5e5f54210292', '19dcawoKcZdQz365WpXWMhX6QCUpR9SY4r'), + ('053f210e7277c899c3a155cc1c90f4106cbddeec6e', '37Sp6Rv3y4kVd1nQ1JV5pfqXccHNyZm1x3'), + ('6fc8a3c2a09a298592c3e180f02487cd91ba3400b5', 'myoqcgYiehufrsnnkqdqbp69dddVDMopJu'), + ('c499b31df7c9068d1481b596578ddbb4d3bd90baeb', '2N7FuwuUuoTBrDFdrAZ9KxBmtqMLxce9i1C'), + ('80c7666842503db6dc6ea061f092cfb9c388448629a6fe868d068c42a488b478ae', '5KL6zEaMtPRXZKo1bbMq7JDjjo1bJuQcsgL33je3oY8uSJCR5b4'), + ('8007f0803fc5399e773555ab1e8939907e9badacc17ca129e67a2f5f2ff84351dd01', 'KwV9KAfwbwt51veZWNscRTeZs9CKpojyu1MsPnaKTF5kz69H1UN2'), + ('efea577acfb5d1d14d3b7b195c321566f12f87d2b77ea3a53f68df7ebf8604a801', '93N87D6uxSBzwXvpokpzg8FFmfQPmvX4xHoWQe3pLdYpbiwT5YV'), + ('ef0b3b34f0958d8a268193a9814da92c3e8b58b4a4378a542863e34ac289cd830c01', 'cMxXusSihaX58wpJ3tNuuUcZEQGt6DKJ1wEpxys88FFaQCYjku9h'), + ('001ed467017f043e91ed4c44b4e8dd674db211c4e6', '13p1ijLwsnrcuyqcTvJXkq2ASdXqcnEBLE'), + ('055ece0cadddc415b1980f001785947120acdb36fc', '3ALJH9Y951VCGcVZYAdpA3KchoP9McEj1G'), + ] + + def test_decode_check(self): + for a, b in self.vectors: + self.assertEqual(trezor.crypto.base58.decode_check(b), trezor.utils.unhexlify('a')) + + def test_encode_check(self): + for a, b in self.vectors: + self.assertEqual(trezor.crypto.base58.encode_check(trezor.utils.unhexlify('a')), b) + +if __name__ == '__main__': + unittest.main() diff --git a/src/tests/test_crypto_sha256.py b/src/tests/test_crypto_sha256.py new file mode 100644 index 00000000..93c14359 --- /dev/null +++ b/src/tests/test_crypto_sha256.py @@ -0,0 +1,19 @@ +import sys +sys.path.append('..') +sys.path.append('../lib') +import unittest +import trezor.utils + +import trezor.crypto.sha256 + +class TestCryptoSha256(unittest.TestCase): + + # vectors from http://www.di-mgt.com.au/sha_testvectors.html + def test_hash(self): + self.assertEqual(trezor.crypto.sha256.hash(b''), trezor.utils.unhexlify('e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855')) + self.assertEqual(trezor.crypto.sha256.hash(b'abc'), trezor.utils.unhexlify('ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad')) + self.assertEqual(trezor.crypto.sha256.hash(b'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'), trezor.utils.unhexlify('248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1')) + self.assertEqual(trezor.crypto.sha256.hash(b'abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu'), trezor.utils.unhexlify('cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1')) + +if __name__ == '__main__': + unittest.main() diff --git a/src/tests/test_crypto_sha512.py b/src/tests/test_crypto_sha512.py new file mode 100644 index 00000000..7180c78d --- /dev/null +++ b/src/tests/test_crypto_sha512.py @@ -0,0 +1,19 @@ +import sys +sys.path.append('..') +sys.path.append('../lib') +import unittest +import trezor.utils + +import trezor.crypto.sha512 + +class TestCryptoSha512(unittest.TestCase): + + # vectors from http://www.di-mgt.com.au/sha_testvectors.html + def test_hash(self): + self.assertEqual(trezor.crypto.sha512.hash(b''), trezor.utils.unhexlify('cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e')) + self.assertEqual(trezor.crypto.sha512.hash(b'abc'), trezor.utils.unhexlify('ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f')) + self.assertEqual(trezor.crypto.sha512.hash(b'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'), trezor.utils.unhexlify('204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445')) + self.assertEqual(trezor.crypto.sha512.hash(b'abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu'), trezor.utils.unhexlify('8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909')) + +if __name__ == '__main__': + unittest.main() diff --git a/src/trezor/crypto/sha512.py b/src/trezor/crypto/sha512.py new file mode 100644 index 00000000..55cd5222 --- /dev/null +++ b/src/trezor/crypto/sha512.py @@ -0,0 +1,6 @@ +from TrezorCrypto import Sha512 + +_sha512 = Sha512() + +def hash(data): + return _sha512.hash(data)