Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce TROPIC01 from Tropic Square #4172

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open

Conversation

ibz
Copy link
Contributor

@ibz ibz commented Sep 11, 2024

  • This PR adds libtropic as a dependency to the build and exposes its functionality to Micropython
  • On unix (Trezor emulator), libtropic requires the Tropic model to be running and accepting connections over TCP, while the actual device libtropic will talk directly to the TROPIC01 chip
  • A smoke test is provided as part of the unit tests under core/
  • The emulator launcher (emu.py) has a way to run the Tropic model automatically - this is for convenience, when testing manually using the emulator. The added benefit of this is little actually, since one could just run it separately with a single command, but having emu.py do it also serves as a way to document (via code) how it is done and to enforce a path where everybody using the model will have it set up, for consistency.

#4718 is a follow-up for this PR, so we can merge this even before we have access to the open sourced model!

@ibz ibz self-assigned this Sep 11, 2024
"vendor/libtropic/src/lt_l2_frame_check.c",
"vendor/libtropic/src/lt_l3.c",
"vendor/libtropic/src/lt_random.c",
"vendor/libtropic/hal//port/unix/lt_port_unix.c",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"vendor/libtropic/hal//port/unix/lt_port_unix.c",
"vendor/libtropic/hal/port/unix/lt_port_unix.c",

"vendor/libtropic/hal/crypto/trezor_crypto/lt_crypto_trezor_sha256.c",
"vendor/libtropic/hal/crypto/trezor_crypto/lt_crypto_trezor_x25519.c",
]
defines += ["USE_TREZOR_CRYPTO"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should be USE_TS_CRYPTO, no?

Suggested change
defines += ["USE_TREZOR_CRYPTO"]
defines += ["USE_TS_CRYPTO"]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should be USE_TS_CRYPTO, no?

Actually, I was thinking not, because this is the emulator build, in which we would still use TREZOR_CRYPTO, because the TS chip is not there. What do you think, @pavelpolach321?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In libtropic we use USE_TREZOR_CRYPTO define if we want to use trezor_crypto as a source of cryptographic code which is running on host (MCU) side.

Example: https://github.com/tropicsquare/libtropic/blob/master/hal/crypto/trezor_crypto/ts_trezor_x25519.c#L1

Therefore I think that passing USE_TREZOR_CRYPTO to compilation of libtropic files will work in your case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we are good on this?

Comment on lines 106 to 99
vstr_t vstr = {0};
vstr_init_len(&vstr, 1024);

bytes_to_chars(X509_cert, vstr.buf, 512);

return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr);
Copy link
Member

@prusnak prusnak Sep 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why convert to hex here? IMHO just return raw binary bytes and if we need hex we can call foo.hex() in Python.

Suggested change
vstr_t vstr = {0};
vstr_init_len(&vstr, 1024);
bytes_to_chars(X509_cert, vstr.buf, 512);
return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr);
return mp_obj_new_str_copy(&mp_type_str, X509_cert, 512);

@ibz ibz force-pushed the ibz/20240805-libtropic branch 3 times, most recently from f41ec36 to e91f6cb Compare September 16, 2024 15:07
@ibz ibz force-pushed the ibz/20240805-libtropic branch 3 times, most recently from c9485c3 to 47d4427 Compare September 23, 2024 14:45
@ibz ibz force-pushed the ibz/20240805-libtropic branch 2 times, most recently from 4b65b70 to 94517e4 Compare September 25, 2024 16:47
@ibz ibz marked this pull request as ready for review September 26, 2024 09:58
@ibz ibz requested a review from matejcik as a code owner September 26, 2024 09:58
@ibz ibz requested a review from andrewkozlik September 26, 2024 09:58
@ibz ibz force-pushed the ibz/20240805-libtropic branch from 94517e4 to 3c77bf0 Compare October 2, 2024 09:00
@onvej-sl
Copy link
Contributor

onvej-sl commented Oct 9, 2024

Hello @ibz, I wanted to ask if you could use fixup commits instead of force-pushing. Fixup commits make it easier to track which suggestions have already been addressed. We usually squash the fixup commits just before merging the pull request.

@onvej-sl
Copy link
Contributor

onvej-sl commented Oct 9, 2024

As one of the developers involved in integrating Optiga into Trezor, I would like to document the architecture we used for the integration. The following diagrams shows the dependencies of all relevant functions where Optiga is utilized:

graph TD
    subgraph optiga_transport["core∕embed∕trezorhal∕optiga∕optiga_transport.c"]
    optiga_sec_chan_handshake
    optiga_init
    optiga_execute_command
    end
    subgraph optiga_commands["core∕embed∕trezorhal∕optiga∕optiga_commands.c"]
    optiga_calc_sign --> optiga_execute_command     
    optiga_get_data_object --> optiga_execute_command    
    optiga_set_autostate --> optiga_execute_command  

    optiga_get_random --> optiga_execute_command             
    end    
    subgraph optiga["core∕embed∕trezorhal∕optiga∕optiga.c"]
    optiga_sign --> optiga_calc_sign
    optiga_read_cert --> optiga_get_data_object
    optiga_random_buffer --> optiga_get_random
    optiga_pin_verify --> optiga_set_autostate    
    end
    subgraph storage["storage∕storage.c"]
    storage_unlock --> optiga_pin_verify
    end
    subgraph modoptiga["core∕embed∕extmod∕modtrezorcrypto∕modtrezorcrypto_optiga.h"]
    sign --> optiga_sign
    get_cert --> optiga_read_cert
    end
    subgraph modconfig["core∕embed∕extmod∕modtrezorconfig∕modtrezorconfig.c"]
    unlock --> storage_unlock
    end
    subgraph modrandom["core∕embed∕extmod∕modtrezorcrypto∕modtrezorcrypto_random.c"]
    random_bytes --> optiga_random_buffer
    end    
    subgraph modmain["core∕embed∕kernel∕main.c"]
    driver_init --> optiga_sec_chan_handshake
    driver_init --> optiga_init
    end
Loading

Note that the functions optiga_sec_chan_handshake and optiga_init (which are perhaps analogous to tropic_init) are defined outside python modules (core/embed/extmod/**). The reason for this is that we call them from core/embed/kernel/main.c. However, I'm not certain if this is sufficient justification, since it seems to me that they could be called only after the micropython environment is initialized.

I am not claiming that the architecture is perfect nor that there are no reasons to integrate Tropic differently. However, it would be nice, if we could keep it consistent.

One significant difference is that we don't have an Optiga emulator. How will the code in this pull request differ if we use a physical device instead of an emulator?

@ibz ibz force-pushed the ibz/20240805-libtropic branch from 3c77bf0 to 7283ff6 Compare November 4, 2024 12:11
@ibz
Copy link
Contributor Author

ibz commented Nov 4, 2024

Hello @ibz, I wanted to ask if you could use fixup commits instead of force-pushing. Fixup commits make it easier to track which suggestions have already been addressed. We usually squash the fixup commits just before merging the pull request.

Got it, sorry for that! BTW, I just force-pushed again, because I rebased on main. There's no way around that, I suppose... But I will do fixup commits from now on.

@ibz
Copy link
Contributor Author

ibz commented Nov 4, 2024

Note that the functions optiga_sec_chan_handshake and optiga_init (which are perhaps analogous to tropic_init) are defined outside python modules (core/embed/extmod/**). The reason for this is that we call them from core/embed/kernel/main.c. However, I'm not certain if this is sufficient justification, since it seems to me that they could be called only after the micropython environment is initialized.

Indeed, I can move tropic_init out of the Python module. We should probably call it in the same place where we call optiga_init, which is in main.c.

I am not claiming that the architecture is perfect nor that there are no reasons to integrate Tropic differently. However, it would be nice, if we could keep it consistent.

By this did you mean just the above part, about tropic_init? Or the other difference - that optiga comes under trezorhal whereas tropic does not?

The way I see it ... we have the optiga HAL (for the real chip) and the "unix" optiga HAL (for the emulator) - which are two different things. But in the case of tropic, libtropic is what provides the abstraction - because it can talk either to the real chip, or to the model, over TCP. So there would be no HAL layer (or - rather - libtropic is the HAL). This is why I went to just expose libtropic to Python without adding anything under trezorhal. But I can do it otherwise, if you think that is a good idea.

@onvej-sl
Copy link
Contributor

onvej-sl commented Nov 5, 2024

Indeed, I can move tropic_init out of the Python module. We should probably call it in the same place where we call optiga_init, which is in main.c.

One reason for initializing optiga in main.c was that the handshake process uses a secret stored in a memory location that becomes inaccessible once the firmware enters unprivileged mode. It seems to me that this concept applies to tropic as well. It would be beneficial to store the production handshake keys in this area.

But in the case of tropic, libtropic is what provides the abstraction - because it can talk either to the real chip, or to the model, over TCP.

This is something I don't quite understand. Does it mean that libtropic itself manages the communication with the chip? How can I instruct libtropic to use a real chip instead of an emulator? How can I specify which interface (for example I2C bus index) it should use for communication with tropic.

@pavelpolach321
Copy link

pavelpolach321 commented Nov 5, 2024

Indeed, I can move tropic_init out of the Python module. We should probably call it in the same place where we call optiga_init, which is in main.c.

One reason for initializing optiga in main.c was that the handshake process uses a secret stored in a memory location that becomes inaccessible once the firmware enters unprivileged mode. It seems to me that this concept applies to tropic as well. It would be beneficial to store the production handshake keys in this area.

But in the case of tropic, libtropic is what provides the abstraction - because it can talk either to the real chip, or to the model, over TCP.

This is something I don't quite understand. Does it mean that libtropic itself manages the communication with the chip? How can I instruct libtropic to use a real chip instead of an emulator? How can I specify which interface (for example I2C bus index) it should use for communication with tropic.

Hello, libtropic manages the communication by using its platform specific c file during compilation. For compiling for embedded target, use this file in your makefile: https://github.com/tropicsquare/libtropic/blob/develop/hal/port/stm32/lt_port_stm32.c - have a look from line 241 to the end.
For compiling for unix environment (your emulator), where you expect libtropic to talk to TROPIC01 model over tcp, then use this file
https://github.com/tropicsquare/libtropic/blob/develop/hal/port/unix/lt_port_unix.c

edit: you could also define transport layer's primitives for your embedded environment. For example within your codebase create lt_port_trezor.c and fill it with definitions for interfaces described by this header file: https://github.com/tropicsquare/libtropic/blob/develop/include/libtropic_port.h

@onvej-sl
Copy link
Contributor

Thank you for the explanation. It makes sense to me now. This line is what causes the build to use an emulator instead of a physical device.

I still don't understand whether we have to perform a handshake before every command. See this comment. Trezor with optiga performs a handshake once during startup and then reuses the handle. Is there any benefit in performing the handshake for each individual command?

@ibz
Copy link
Contributor Author

ibz commented Nov 18, 2024

I still don't understand whether we have to perform a handshake before every command. See this comment. Trezor with optiga performs a handshake once during startup and then reuses the handle. Is there any benefit in performing the handshake for each individual command?

We don't. That is my mistake. Will fix.

@onvej-sl
Copy link
Contributor

onvej-sl commented Nov 19, 2024

My suggestion is to:

  1. Store SHiPRIV_BYTES in core/embed/sec/secret/unix/secret.c.
  2. Introduce a function, secret_tropic_get_privkey, in core/embed/sec/secret/unix/secret.c. This function will return SHiPRIV_BYTES. This can be built on top of this pull request.
  3. Introduce a function, tropic_handshake(const uint8_t *trezor_privkey), in core/embed/sec/tropic/tropic_transport.c. This function will compute the public key using curve25519_scalarmult_basepoint, call lt_handshake, and store the handle.
  4. In core/embed/projects/kernel/main.c, call tropic_handshake and lt_init.

Once we want to use a physical tropic, we will implement core/embed/sec/secret/model_with_tropic/secret.c, and use port_model_with_tropic.c instead of lt_port_unix.c.

The tropic commands can then be called either directly using libtropic and the handle in tropic_transport.c, or through wrappers that will be in tropic_commands.c.

I am still uncertain about the variable naming. Does this approach make sense to you?

@ibz ibz force-pushed the ibz/20240805-libtropic branch from 7d1b655 to 413e9b1 Compare February 28, 2025 11:37
Comment on lines 78 to 82
########
# USE_TROPIC should eventually be enabled when building with the 'tropic' feature
# but for now we don't have the model running, which would crash the emulator on startup.
# defines += ["USE_TROPIC=1"]
########
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand it correctly, this implies that there is no test in the CI that would at least ensure this pull request compiles without errors.

What do you think about

  • introducing a new model that would be the same as T3W1 but with USE_TROPIC=1 and
  • adding a pipeline that will build the emulator for this model?

Copy link
Contributor Author

@ibz ibz Feb 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, I agree this is not ideal (not building it at least).

I like your idea actually, and that could be good in the future also, not only for building the tropic code as part of the CI.

I assume most development locally would be done against T3W1 "plain" and the T3W1_TROPIC emulator would only be started when needed (to debug code that interacts with Tropic), because it needs the model running and it is not necessary for most firmware work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

USE_TROPIC=1 is set for T3W1 hardware builds so that ensures general buildability of this code.

i don't really see a point of building this for emulator until we have the model widely available, and if the code somehow drifts between now and then, we'll fix it then. merging now, without CI involvement, is much better than letting it linger for longer (heh)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

USE_TROPIC=1 is set for T3W1 hardware builds so that ensures general buildability of this code.

No. As far as I know, USE_TROPIC=1 is not set for T3W1, because "tropic" is not listed in FEATURES_WANTED. And the build actually fails if I list it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i see, you're right. should we add "tropic" in SConscript.firmware then? cc @TychoVrahe

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does tropic_init() actually do?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three more things:

  1. I don't like that you have to launch the the tropic emulator manually if you want to run the device tests locally.
  2. We must not forget about device tests, they will also require the tropic emulator.
  3. The tropic emulator is statefull and we want to reset the state between individual tests.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3. The tropic emulator is statefull and we want to reset the state between individual tests.

this should be part of WipeDevice flow, and we'll need it as a feature of the firmware

2. We must not forget about device tests, they will also require the tropic emulator.

i'm thinking that this may be a good reason to introduce "T3W1 without tropic" as a separate matrix item, so that we can run the test suite in CI without involving the model. (but for now it's not super important, given that the model isn't actually doing anything)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does tropic_init() actually do?

It sets up the secure channel between trezor and tropic.

this should be part of WipeDevice flow, and we'll need it as a feature of the firmware

Yes, but WipeDevice resolves the issue in the device tests but not in the unit tests.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but WipeDevice resolves the issue in the device tests but not in the unit tests.

right, so there should be a function exposed to python to do this (which will also get called in the WipeDevice flow)
and Tropic unit tests will do a setUp() that calls this?

@ibz ibz mentioned this pull request Mar 3, 2025
2 tasks
@ibz ibz force-pushed the ibz/20240805-libtropic branch 5 times, most recently from 8ee1b8a to 2ec58c2 Compare March 7, 2025 12:00
@ibz ibz force-pushed the ibz/20240805-libtropic branch from 2ec58c2 to 5034e58 Compare March 7, 2025 12:03
@ibz ibz force-pushed the ibz/20240805-libtropic branch from 5034e58 to 404544b Compare March 7, 2025 12:05
@ibz ibz force-pushed the ibz/20240805-libtropic branch 3 times, most recently from 419e257 to c757bf6 Compare March 7, 2025 12:29
@ibz ibz force-pushed the ibz/20240805-libtropic branch from c757bf6 to c7aae24 Compare March 7, 2025 12:47
@@ -196,6 +196,7 @@ jobs:
submodules: recursive
- uses: ./.github/actions/environment
- run: nix-shell --run "poetry run make -C core build_unix"
- run: nix-shell --run "cd vendor/ts-tvl/tvl/server/model_config && poetry install && poetry run model_server tcp -c model_config.yml &"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think poetry install can be omitted. The python environment should already be installed at this point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: 🏃‍♀️ In progress
Development

Successfully merging this pull request may close these issues.

7 participants