diff --git a/CHANGELOG.md b/CHANGELOG.md index 71f2384..a9ab648 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,21 +5,34 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.0.0] - 2021-08-02 +## [Unreleased] ### Added -- Inital commit with the brand new Hathor application -- Sign TX command -- Get XPUB command -- Confirm address command -- HTR + Version command +- Support for authority outputs +- Support for mint/melt/delegate/destroy operations -## [1.0.1] - 2021-09-22 +## Changed + +- Signal bits and version parsed separetely, version is now uint8_t instead of uint16_t. +- In sign tx command we check that change output index is valid (inside the output array) and not repeated. + +## Fixed + +- Auto confirmation of unreceived change outputs would fail tx signing +- Some parsing errors would not clean the global context, it could fail the next command. + +## [1.1.1] - 2024-03-12 ### Added -- Support for Nano X +- CodeQL security check in CI + +### Security + +- Code improvements to prevent null pointer exception and buffer overflow. +- Update deprecated OS calls +- Clean dead code ## [1.1.0] - 2022-02-4 @@ -34,14 +47,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Sign TX command now supports transactions with custom tokens -## [1.1.1] - 2024-03-12 +## [1.0.1] - 2021-09-22 ### Added -- CodeQL security check +- Support for Nano X + +## [1.0.0] - 2021-08-02 + +### Added + +- Inital commit with the brand new Hathor application +- Sign TX command +- Get XPUB command +- Confirm address command +- HTR + Version command + -### Changed -- Code improvements to prevent null pointer exception and buffer overflow. -- Update deprecated OS calls -- Clean dead code diff --git a/doc/BUILD.md b/doc/BUILD.md index f7688c6..cc4b44a 100644 --- a/doc/BUILD.md +++ b/doc/BUILD.md @@ -8,20 +8,29 @@ All dev commands are configured on a separate `.dev.Makefile`. ## Dev-env This command will start a docker container with the configured dev-env where you can run any commands on the `Makefile` or the SDK, like [loading](https://developers.ledger.com/docs/nano-app/load/) the app on Nano S. -`make -f .dev.Makefile builder` + + +```bash +make -f .dev.Makefile builder +``` ## Compilation -`make -f .dev.Makefile build` +```bash +make -f .dev.Makefile build +``` You can add the flag `DEBUG=1` at the end to compile on debug mode. To remove all files generated from the build process run: -`make -f .dev.Makefile clean` +```bash +make -f .dev.Makefile clean +``` ## Linter Build linter with `make -f .dev.Makefile lint-build` and you can use the same linter (and linter configurations) as the CI using the commands: + - `make -f .dev.Makefile lint` - `make -f .dev.Makefile lint-fix` diff --git a/src/token/token_parser.c b/src/token/token_parser.c index 1c9ff25..97a9bcc 100644 --- a/src/token/token_parser.c +++ b/src/token/token_parser.c @@ -45,5 +45,6 @@ int8_t find_token_registry_index(uint8_t *uid) { for (uint8_t i = 0; i < G_token_symbols.len; i++) { if (memcmp(uid, G_token_symbols.tokens[i].uid, TOKEN_UID_LEN) == 0) return i; } + PRINTF("[*] Registry length = %d\n", G_token_symbols.len); return -1; } diff --git a/tests/README.md b/tests/README.md index 9221e2f..8c169e5 100644 --- a/tests/README.md +++ b/tests/README.md @@ -11,18 +11,40 @@ poetry install make install ``` -### Launch with Speculos +### Run with Speculos -First start your application with Speculos +First you need to build and run the application with speculos. +On the root directory of the project run: + +```bash +make -f .dev.Makefile build DEBUG=1 ``` -./path/to/speculos.py /path/to/app.elf --ontop --sdk 2.0 + +```bash +make -f .dev.Makefile speculos ``` -then in the `tests` folder run +### Running tests -``` +In the `tests` folder run: + +```bash +make test +# or poetry run pytest +``` + +### Running qa + +You need to open the speculos web ui, the actual address will be shown in the speculos stdout. + +In the `tests` folder run: + +```bash +make qa # or -make test +poetry run pytest qa.py ``` + +Obs: you need to restart speculos if the tests ran before since they configure automation rules which need to be turned off during qa. diff --git a/tests/app_client/cmd.py b/tests/app_client/cmd.py index 7d0176b..627b685 100644 --- a/tests/app_client/cmd.py +++ b/tests/app_client/cmd.py @@ -16,6 +16,7 @@ def __init__(self, transport: ApduTransport, debug: bool = False) -> None: self.transport = transport def get_app_and_version(self) -> Tuple[str, str]: + print("[+] sending: get_app_and_version request") sw, response = self.transport.exchange_apdu_raw( self.builder.get_app_and_version() ) # type: int, bytes @@ -44,6 +45,7 @@ def get_app_and_version(self) -> Tuple[str, str]: return app_name, version def get_version(self) -> Tuple[bytes, int, int, int]: + print("[+] sending: get_version request") sw, response = self.transport.exchange_apdu_raw(self.builder.get_version()) if sw != 0x9000: @@ -62,6 +64,7 @@ def get_version(self) -> Tuple[bytes, int, int, int]: def get_address(self, bip32_path: str) -> str: + print("[+] sending: get_address request") sw, response = self.transport.exchange_apdu_raw( self.builder.get_address(bip32_path) ) @@ -72,6 +75,7 @@ def get_address(self, bip32_path: str) -> str: return def get_xpub(self, bip32_path: str) -> Tuple[bytes, bytes]: + print("[+] sending: get_xpub request") sw, response = self.transport.exchange_apdu_raw( self.builder.get_xpub(bip32_path=bip32_path) ) @@ -108,11 +112,15 @@ def sign_tx( response: bytes = b"" signatures: List[bytes] = [] + + index = 0 for chunk in self.builder.sign_tx_send_data( transaction=transaction, change_list=change_list, use_old_protocol=use_old_protocol, ): + print("[+] sending: sign_tx send for chunk {}\n".format(index)) + index += 1 sw, response = self.transport.exchange_apdu_raw(chunk) print("\n", "ledger_resp:", sw, response) @@ -120,8 +128,12 @@ def sign_tx( raise DeviceException(error_code=sw, ins=InsType.INS_SIGN_TX) time.sleep(0.1) + + index = 0 # ask for signatures for chunk in self.builder.sign_tx_signatures(transaction): + print("[+] sending: sign_tx sign for chunk {}\n".format(index)) + index += 1 sw, response = self.transport.exchange_apdu_raw(chunk) print("\n", "ledger_resp:", sw, response) @@ -138,6 +150,7 @@ def sign_tx( return signatures def sign_token_data(self, token: Token) -> bytes: + print("[+] sending: sign_token_data request") sw, response = self.transport.exchange_apdu_raw( self.builder.sign_token_data(token) ) @@ -148,6 +161,7 @@ def sign_token_data(self, token: Token) -> bytes: return response def send_token_data(self, token: Token, signature: bytes, num: int = 0): + print("[+] sending: send_token_data request") sw, response = self.transport.exchange_apdu_raw( self.builder.send_token_data(token, signature, num=num) ) @@ -158,9 +172,15 @@ def send_token_data(self, token: Token, signature: bytes, num: int = 0): def send_token_data_list(self, tokens: List[Token], signatures: List[bytes]): assert len(tokens) == len(signatures) for i, token in enumerate(tokens): + print( + "[+] sending: send_token_data_list request for token {} - {}".format( + i, token + ) + ) self.send_token_data(token, signatures[i], num=i) def verify_token_signature(self, token: Token, signature: bytes): + print("[+] sending: verify_token_signature request") sw, response = self.transport.exchange_apdu_raw( self.builder.verify_token_signature(token, signature) ) @@ -169,6 +189,7 @@ def verify_token_signature(self, token: Token, signature: bytes): raise DeviceException(error_code=sw, ins=InsType.INS_GET_ADDRESS) def reset_token_signatures(self): + print("[+] sending: reset_token_signatures request") sw, response = self.transport.exchange_apdu_raw( self.builder.reset_token_signatures() ) diff --git a/tests/qa.py b/tests/qa.py index b97eb44..b9964f8 100644 --- a/tests/qa.py +++ b/tests/qa.py @@ -12,7 +12,7 @@ def test_qa_version(cmd): print("QA::version") - assert cmd.get_version() == (b'HTR', 1, 0, 0) + assert cmd.get_version() == (b'HTR', 1, 1, 1) def test_qa_address(cmd): @@ -48,7 +48,39 @@ def test_qa_reset_token_signatures(cmd): cmd.reset_token_signatures() -def test_qa_sign_tx_with_token(cmd): +def test_qa_sign_tx_custom_tokens(cmd): + path = "m/44'/280'/0'/0/10" + token1 = fake_token() + token2 = fake_token() + inputs = [ + TxInput(fake.sha256(True), 1, path), + TxInput(fake.sha256(True), 0, path), + ] + outputs = [ + TxOutput(fake.pyint(1), fake_script(), 1), + TxOutput(fake.pyint(1), fake_script(), 2), + TxOutput(fake.pyint(1), fake_script()), + ] + tx = fake_tx(inputs=inputs, outputs=outputs, tokens=[token1.uid, token2.uid]) + print("QA::sign_tx_custom_tokens::token1:", str(token1)) + print("QA::sign_tx_custom_tokens::token2:", str(token2)) + print("QA::sign_tx_custom_tokens::tx:", str(tx)) + + print("QA::sign_tx_custom_tokens::sign::token1") + sig1 = cmd.sign_token_data(token1) + print("QA::sign_tx_custom_tokens::sign::token1") + sig2 = cmd.sign_token_data(token2) + + print("QA::sign_tx_custom_tokens::send::token1") + cmd.send_token_data(token1, sig1, 0) + print("QA::sign_tx_custom_tokens::send::token2") + cmd.send_token_data(token2, sig2, 1) + + print("QA::sign_tx_custom_tokens::sign::tx") + signatures = cmd.sign_tx(tx) + + +def test_qa_sign_tx_with_authority(cmd): path = "m/44'/280'/0'/0/10" # sign_token token = fake_token() @@ -57,12 +89,13 @@ def test_qa_sign_tx_with_token(cmd): TxInput(fake.sha256(True), 0, path), ] outputs = [ - TxOutput(fake.pyint(1), fake_script(), 1), + TxOutput(1, fake_script(), 1, True), + TxOutput(2, fake_script(), 1, True), TxOutput(fake.pyint(1), fake_script()), ] - tx = Transaction(1, [token.uid], inputs, outputs) - print("QA::sign_tx_with_token::token:", str(token)) - print("QA::sign_tx_with_token::tx:", str(tx)) + tx = Transaction(0, 1, [token.uid], inputs, outputs) + print("QA::sign_tx_with_authority::token:", str(token)) + print("QA::sign_tx_with_authority::tx:", str(tx)) sig = cmd.sign_token_data(token) # send_token_data cmd.send_token_data(token, sig) diff --git a/tests/utils.py b/tests/utils.py index d38fa48..527b510 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -32,8 +32,8 @@ def fake_tx( outputs: Optional[TxOutput] = None, tokens: Optional[bytes] = None, ) -> Transaction: - tx_inps = inputs or [fake_input() for _ in range(fake.pyint(1, 10))] - tx_outps = outputs or [fake_output() for _ in range(fake.pyint(1, 10))] + tx_inps = inputs or [fake_input() for _ in range(fake.pyint(1, 5))] + tx_outps = outputs or [fake_output() for _ in range(fake.pyint(1, 5))] tkns = ( [fake.sha256(True) for _ in range(fake.pyint(1, 10))] if tokens is None