feat(core): functions for Evolu spam protection
What changed, and why it matters
This commit adds new Trezor firmware features for an 'Evolu' / 'Suite Sync' service. It introduces a device-bound delegated identity key, a way to export that key after user confirmation, and ways to prove possession of that key to request a SLIP-21 node or to sign a registration request with the device's Optiga certificate. The change is framed by the vendor as spam protection / anti-abuse functionality, not as a security fix. The commit itself is a feature implementation, but it touches sensitive areas: key derivation from a new per-device master secret, secure-monitor/kernel syscalls for reading a private key, and user-consent flows for exporting a private key.
Treat this as a high-sensitivity feature change requiring focused review, not as an urgent vulnerability patch. Reviewers should verify: (1) the new master key is generated with sufficient entropy and locked in OTP before any use, (2) the HMAC-based derivation is domain-separated correctly and cannot collide with other key indices, (3) the syscall/smcall verifier prevents arbitrary kernel/secure-world memory writes, (4) the proof-of-possession signature scheme is robust against replay and malleability, (5) the user confirmation for EvoluGetDelegatedIdentityKey cannot be bypassed, and (6) the raw private key is zeroized after use in all paths. No immediate user action is indicated by the commit itself.
Security signals we found
New private-key export message added (EvoluGetDelegatedIdentityKey returns raw nist256p1 private key)
New syscall/smcall added to read delegated identity key from secure world into caller buffer
New per-device master key generated in flash OTP and used for deterministic key derivation
Proof-of-possession signature required before SLIP-21 node export and before Optiga-signed registration
User confirmation dialog added for delegated identity key export ('Suite Sync')
THP credential validation added on THP-enabled devices before key export
Certificate chain parser moved from authenticate_device to apps.common.certificates (shared code)
Legacy firmware skips all Evolu messages
Evidence from the diff
The patch implements three new Evolu messages (EvoluGetNode, EvoluSignRegistrationRequest, EvoluGetDelegatedIdentityKey) and a delegated identity key subsystem. A new per-device master key is generated in flash OTP (FLASH_OTP_BLOCK_MASTER_KEY) and used to derive an ECDSA NIST P-256 private key via HMAC-SHA256 with a fixed key index. The key is exposed to MicroPython through trezorutils.delegated_identity(), and to the unprivileged application via new syscall/smcall gates with a probe_write_access verifier. The apps/evolu handlers require a proof-of-possession signature over a protocol-specific header (and sometimes arguments) before returning the SLIP-21 node or signing a registration request with Optiga. The previous EvoluGetNode handler that showed a generic confirmation is replaced by the proof-based flow. The new EvoluGetDelegatedIdentityKey message shows a confirmation dialog and, on THP-enabled devices, validates a THP credential before returning the raw private key.
Changed components
core/src/apps/evolu/*core/embed/sec/secret/*core/embed/sys/syscall/stm32/*core/embed/sys/smcall/stm32/*core/embed/upymod/modtrezorutils/modtrezorutils.ccommon/protob/messages-evolu.protocore/src/apps/workflow_handlers.pypython/src/trezorlib/evolu.pypython/src/trezorlib/cli/evolu.pyInspect captured patch +2455 / −228
diff --git a/common/protob/messages-evolu.proto b/common/protob/messages-evolu.proto
index ef4b5dd7..47aca3d0 100644
--- a/common/protob/messages-evolu.proto
+++ b/common/protob/messages-evolu.proto
@@ -1,15 +1,14 @@
-
syntax = "proto2";
package hw.trezor.messages.evolu;
-import "options.proto";
-
-option (include_in_bitcoin_only) = true;
-
// Sugar for easier handling in Java
option java_package = "com.satoshilabs.trezor.lib.protobuf";
option java_outer_classname = "TrezorMessageEvolu";
+import "options.proto";
+
+option (include_in_bitcoin_only) = true;
+
/**
* Request: Ask the device for the SLIP-21 node for Evolu, a local first storage
* framework. See https://github.com/evoluhq/evolu
@@ -17,6 +16,7 @@ option java_outer_classname = "TrezorMessageEvolu";
* @next EvoluNode
*/
message EvoluGetNode {
+ required bytes proof_of_delegated_identity = 1;
}
/**
@@ -26,3 +26,44 @@ message EvoluGetNode {
message EvoluNode {
required bytes data = 1;
}
+
+/**
+ * Request: Request the device to sign a registration request for Evolu.
+ * @start
+ * @next EvoluRegistrationRequest
+ * @next Failure
+ */
+message EvoluSignRegistrationRequest {
+ required bytes challenge_from_server = 1; // replay attack protection
+ required uint32 size_to_acquire = 2; // number of bytes to acquire
+ required bytes proof_of_delegated_identity = 3;
+}
+
+/**
+ * Response: The signature of the registration request with the Optiga's
+ * certificate chain.
+ * @end
+ */
+message EvoluRegistrationRequest {
+ repeated bytes certificate_chain = 1;
+ required bytes signature = 2;
+}
+
+/**
+ * Request: Request the delegated identity key from the device.
+ * @start
+ * @next EvoluDelegatedIdentityKey
+ * @next Failure
+ */
+message EvoluGetDelegatedIdentityKey {
+ optional bytes thp_credential = 1; // THP credential so that we can display the host on the device
+ optional bytes host_static_public_key = 2; // Host static public key for THP so that we can validate the credentials
+}
+
+/**
+ * Response: The delegated identity key for Evolu.
+ * @end
+ */
+message EvoluDelegatedIdentityKey {
+ required bytes private_key = 1; // nist256p1 private key
+}
diff --git a/common/protob/messages.proto b/common/protob/messages.proto
index 981dc02d..082cebb0 100644
--- a/common/protob/messages.proto
+++ b/common/protob/messages.proto
@@ -343,6 +343,10 @@ enum MessageType {
// Evolu
MessageType_EvoluGetNode = 2100 [(bitcoin_only) = true, (wire_in) = true];
MessageType_EvoluNode = 2101 [(bitcoin_only) = true, (wire_out) = true];
+ MessageType_EvoluSignRegistrationRequest = 2102 [(bitcoin_only) = true, (wire_in) = true];
+ MessageType_EvoluRegistrationRequest = 2103 [(bitcoin_only) = true, (wire_out) = true];
+ MessageType_EvoluGetDelegatedIdentityKey = 2104 [(bitcoin_only) = true, (wire_in) = true];
+ MessageType_EvoluDelegatedIdentityKey = 2105 [(bitcoin_only) = true, (wire_out) = true];
// Benchmark
MessageType_BenchmarkListNames = 9100 [(bitcoin_only) = true];
diff --git a/core/SConscript.firmware b/core/SConscript.firmware
index f224a70c..9bcf7d29 100644
--- a/core/SConscript.firmware
+++ b/core/SConscript.firmware
@@ -732,6 +732,8 @@ if FROZEN:
])
)
+ SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/evolu/*.py'))
+
if BENCHMARK:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/benchmark/*.py'))
diff --git a/core/SConscript.unix b/core/SConscript.unix
index 814dbea0..3be06697 100644
--- a/core/SConscript.unix
+++ b/core/SConscript.unix
@@ -749,6 +749,9 @@ if FROZEN:
SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/zcash_v4.py',
])
)
+
+ SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/evolu/*.py'))
+
if BENCHMARK:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/benchmark/*.py'))
diff --git a/core/embed/models/otp_layout.h b/core/embed/models/otp_layout.h
index 0557f244..178abb45 100644
--- a/core/embed/models/otp_layout.h
+++ b/core/embed/models/otp_layout.h
@@ -28,3 +28,7 @@
#define FLASH_OTP_BLOCK_FIRMWARE_VERSION 5
#define FLASH_OTP_BLOCK_DEVICE_SN 6
#define FLASH_OTP_BLOCK_DEVICE_VARIANT_REWORK 7
+
+#ifndef SECRET_PRIVILEGED_MASTER_KEY_SLOT
+#define FLASH_OTP_BLOCK_MASTER_KEY 8
+#endif
diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h
index 690bacc2..83ff057b 100644
--- a/core/embed/rust/librust_qstr.h
+++ b/core/embed/rust/librust_qstr.h
@@ -772,6 +772,9 @@ static void _librust_qstrs(void) {
MP_QSTR_sd_card__use_different_card;
MP_QSTR_sd_card__wanna_format;
MP_QSTR_sd_card__wrong_sd_card;
+ MP_QSTR_secure_sync__delegated_identity_key_no_thp;
+ MP_QSTR_secure_sync__delegated_identity_key_thp;
+ MP_QSTR_secure_sync__header;
MP_QSTR_select_menu;
MP_QSTR_select_word;
MP_QSTR_select_word_count;
diff --git a/core/embed/rust/src/translations/generated/translated_string.rs b/core/embed/rust/src/translations/generated/translated_string.rs
index 76fcfd77..4f2f05f1 100644
--- a/core/embed/rust/src/translations/generated/translated_string.rs
+++ b/core/embed/rust/src/translations/generated/translated_string.rs
@@ -1557,6 +1557,9 @@ pub enum TranslatedString {
#[cfg(feature = "universal_fw")]
ripple__destination_tag_missing = 1168, // "Destination tag is not set. Typically needed when sending to exchanges."
words__comm_trouble = 1169, // "Your Trezor is having trouble communicating with your connected device."
+ secure_sync__delegated_identity_key_no_thp = 1170, // "Allow Trezor Suite to use Suite Sync with this Trezor?"
+ secure_sync__delegated_identity_key_thp = 1171, // "Allow {0} on {1} to use Suite Sync with this Trezor?"
+ secure_sync__header = 1173, // "Suite Sync"
}
impl TranslatedString {
@@ -4987,6 +4990,9 @@ impl TranslatedString {
#[cfg(feature = "universal_fw")]
(Self::ripple__destination_tag_missing, "Destination tag is not set. Typically needed when sending to exchanges."),
(Self::words__comm_trouble, "Your Trezor is having trouble communicating with your connected device."),
+ (Self::secure_sync__delegated_identity_key_no_thp, "Allow Trezor Suite to use Suite Sync with this Trezor?"),
+ (Self::secure_sync__delegated_identity_key_thp, "Allow {0} on {1} to use Suite Sync with this Trezor?"),
+ (Self::secure_sync__header, "Suite Sync"),
];
#[cfg(feature = "micropython")]
@@ -6110,6 +6116,9 @@ impl TranslatedString {
(Qstr::MP_QSTR_sd_card__use_different_card, Self::sd_card__use_different_card),
(Qstr::MP_QSTR_sd_card__wanna_format, Self::sd_card__wanna_format),
(Qstr::MP_QSTR_sd_card__wrong_sd_card, Self::sd_card__wrong_sd_card),
+ (Qstr::MP_QSTR_secure_sync__delegated_identity_key_no_thp, Self::secure_sync__delegated_identity_key_no_thp),
+ (Qstr::MP_QSTR_secure_sync__delegated_identity_key_thp, Self::secure_sync__delegated_identity_key_thp),
+ (Qstr::MP_QSTR_secure_sync__header, Self::secure_sync__header),
(Qstr::MP_QSTR_send__cancel_sign, Self::send__cancel_sign),
(Qstr::MP_QSTR_send__cancel_transaction, Self::send__cancel_transaction),
(Qstr::MP_QSTR_send__confirm_sending, Self::send__confirm_sending),
diff --git a/core/embed/sec/secret/inc/sec/secret_keys.h b/core/embed/sec/secret/inc/sec/secret_keys.h
index 1eacf2c7..83c44c33 100644
--- a/core/embed/sec/secret/inc/sec/secret_keys.h
+++ b/core/embed/sec/secret/inc/sec/secret_keys.h
@@ -21,6 +21,10 @@
#include <trezor_types.h>
+#include <ecdsa.h>
+
+secbool secret_key_delegated_identity(uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]);
+
#ifdef SECURE_MODE
#ifdef SECRET_MASTER_KEY_SLOT_SIZE
@@ -35,8 +39,6 @@ secbool secret_key_mcu_device_auth(uint8_t dest[MLDSA_SEEDBYTES]);
#ifdef USE_OPTIGA
-#include <ecdsa.h>
-
#define OPTIGA_PAIRING_SECRET_SIZE 32
secbool secret_key_optiga_pairing(uint8_t dest[OPTIGA_PAIRING_SECRET_SIZE]);
secbool secret_key_optiga_masking(uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]);
@@ -45,7 +47,6 @@ secbool secret_key_optiga_masking(uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]);
#ifdef USE_TROPIC
-#include <ecdsa.h>
#include <ed25519-donna/ed25519.h>
secbool secret_key_tropic_public(curve25519_key dest);
@@ -68,12 +69,31 @@ secbool secret_key_nrf_pairing(uint8_t dest[NRF_PAIRING_SECRET_SIZE]);
secbool secret_key_storage_salt(uint16_t fw_type,
uint8_t dest[SECRET_KEY_STORAGE_SALT_SIZE]);
+#define SECRET_KEY_MASTER_KEY_SIZE 32
+
+typedef struct {
+ size_t size;
+ uint8_t bytes[SECRET_KEY_MASTER_KEY_SIZE];
+} secret_key_master_key_t;
+
+/**
+ * Retrieves the generated buffer with the master key.
+ *
+ * If master key has not yet been generated for the device,
+ * it is generated now.
+ *
+ * This key is used to derive additional credential keys (e.g. Evolu).
+ *
+ * @param master_key structure filled with the generated data.
+ */
+secbool secret_key_master_key_get(secret_key_master_key_t* master_key);
+
#endif // SECURE_MODE
#ifdef KERNEL_MODE
#ifdef USE_NRF_AUTH
-secbool secret_validate_nrf_pairing(const uint8_t *message, size_t msg_len,
- const uint8_t *mac, size_t mac_len);
+secbool secret_validate_nrf_pairing(const uint8_t* message, size_t msg_len,
+ const uint8_t* mac, size_t mac_len);
#endif
#endif
diff --git a/core/embed/sec/secret/secret_keys_common.c b/core/embed/sec/secret/secret_keys_common.c
new file mode 100644
index 00000000..379985f1
--- /dev/null
+++ b/core/embed/sec/secret/secret_keys_common.c
@@ -0,0 +1,116 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef SECURE_MODE
+
+#include <sec/secret.h>
+#include <sec/secret_keys.h>
+#include <trezor_model.h>
+#include <trezor_rtl.h>
+
+#include "../storage/storage_salt.h"
+#include "hmac.h"
+#include "memzero.h"
+#include "nist256p1.h"
+#include "secret_keys_common.h"
+
+static void diversify_and_derive(uint16_t index, uint16_t subindex,
+ const uint8_t master_key[SHA256_DIGEST_LENGTH],
+ uint8_t master_key_length,
+ uint8_t dest[SHA256_DIGEST_LENGTH]) {
+ // The diversifier consists of:
+ // - the key derivation index (2 bytes big-endian), which identifies the
+ // purpose of the key,
+ // - the subindex (2 bytes big-endian), which is incremented until the derived
+ // key meets required criteria, and
+ // - the block index (1 byte), which can be used to produce outputs that are
+ // longer than 32 bytes.
+ uint8_t diversifier[] = {index >> 8, index & 0xff, subindex >> 8,
+ subindex & 0xff, 0};
+
+ hmac_sha256(master_key, master_key_length, diversifier, sizeof(diversifier),
+ dest);
+}
+
+secbool secret_key_derive_sym(uint8_t slot, uint16_t index, uint16_t subindex,
+ uint8_t dest[SHA256_DIGEST_LENGTH]) {
+ secbool ret = sectrue;
+
+ secret_key_master_key_t master_key = {.bytes = {0},
+ .size = SECRET_KEY_MASTER_KEY_SIZE};
+
+#ifdef SECRET_PRIVILEGED_MASTER_KEY_SLOT
+ ret = secret_key_get(slot, master_key.bytes, master_key.size);
+#else // SECRET_PRIVILEGED_MASTER_KEY_SLOT
+ if (slot != UNUSED_KEY_SLOT) {
+ ret = secfalse;
+ goto cleanup;
+ }
+ ret = secret_key_master_key_get(&master_key);
+#endif // SECRET_PRIVILEGED_MASTER_KEY_SLOT
+
+ if (ret != sectrue) {
+ goto cleanup;
+ }
+
+ diversify_and_derive(index, subindex, master_key.bytes, master_key.size,
+ dest);
+
+cleanup:
+ memzero(master_key.bytes, master_key.size);
+ return ret;
+}
+
+secbool secret_key_derive_nist256p1(uint8_t slot, uint16_t index,
+ uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]) {
+ // `slot` argument is not used unless SECRET_PRIVILEGED_MASTER_KEY_SLOT is
+ // defined
+
+ _Static_assert(ECDSA_PRIVATE_KEY_SIZE == SHA256_DIGEST_LENGTH);
+
+ secbool ret = sectrue;
+ bignum256 s = {0};
+ for (uint16_t i = 0; i < 10000; i++) {
+ ret = secret_key_derive_sym(slot, index, i, dest);
+ if (ret != sectrue) {
+ goto cleanup;
+ }
+
+ bn_read_be(dest, &s);
+ if (!bn_is_zero(&s) && bn_is_less(&s, &nist256p1.order)) {
+ // Valid private key, we are done.
+ ret = sectrue;
+ goto cleanup;
+ }
+
+ // Invalid private key, we generate the next key in line.
+ }
+
+ // Loop exhausted all attempts without producing a valid private key.
+ ret = secfalse;
+
+cleanup:
+ memzero(&s, sizeof(s));
+ if (ret != sectrue) {
+ memzero(dest, ECDSA_PRIVATE_KEY_SIZE);
+ }
+ return ret;
+}
+
+#endif // SECURE_MODE
diff --git a/core/embed/sec/secret/secret_keys_common.h b/core/embed/sec/secret/secret_keys_common.h
new file mode 100644
index 00000000..5c8540c8
--- /dev/null
+++ b/core/embed/sec/secret/secret_keys_common.h
@@ -0,0 +1,51 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <trezor_types.h>
+
+#ifdef SECURE_MODE
+
+#include <trezor_model.h>
+#include <trezor_rtl.h>
+
+// Key derivation indices
+#define KEY_INDEX_MCU_DEVICE_AUTH 0
+#define KEY_INDEX_OPTIGA_PAIRING 1
+#define KEY_INDEX_OPTIGA_MASKING 2
+#define KEY_INDEX_TROPIC_PAIRING_UNPRIVILEGED 3
+#define KEY_INDEX_TROPIC_PAIRING_PRIVILEGED 4
+#define KEY_INDEX_TROPIC_MASKING 5
+#define KEY_INDEX_NRF_PAIRING 6
+#define KEY_INDEX_STORAGE_SALT 7
+#define KEY_INDEX_DELEGATED_IDENTITY 8
+
+#ifndef SECRET_PRIVILEGED_MASTER_KEY_SLOT
+#define UNUSED_KEY_SLOT 0
+// This is a dummy value used instead of SECRET_PRIVILEGED_MASTER_KEY_SLOT
+#endif // SECRET_PRIVILEGED_MASTER_KEY_SLOT
+
+secbool secret_key_derive_sym(uint8_t slot, uint16_t index, uint16_t subindex,
+ uint8_t dest[SHA256_DIGEST_LENGTH]);
+
+secbool secret_key_derive_nist256p1(uint8_t slot, uint16_t index,
+ uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]);
+
+#endif // SECURE_MODE
diff --git a/core/embed/sec/secret/stm32f4/secret_keys.c b/core/embed/sec/secret/stm32f4/secret_keys.c
index 5d3af1e9..72c65e9c 100644
--- a/core/embed/sec/secret/stm32f4/secret_keys.c
+++ b/core/embed/sec/secret/stm32f4/secret_keys.c
@@ -25,11 +25,45 @@
#include <sec/secret.h>
#include <sec/secret_keys.h>
+#include "../secret_keys_common.h"
+
+#include <sec/rng.h>
+#include <sys/mpu.h>
+#include <util/flash_otp.h>
+#include "memzero.h"
#ifdef USE_OPTIGA
+
secbool secret_key_optiga_pairing(uint8_t dest[OPTIGA_PAIRING_SECRET_SIZE]) {
return secret_key_get(SECRET_OPTIGA_SLOT, dest, OPTIGA_PAIRING_SECRET_SIZE);
}
-#endif
-#endif
+#endif // USE_OPTIGA
+
+secbool secret_key_delegated_identity(uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]) {
+ secret_key_derive_nist256p1(UNUSED_KEY_SLOT, KEY_INDEX_DELEGATED_IDENTITY,
+ dest);
+ return sectrue;
+}
+
+secbool secret_key_master_key_get(secret_key_master_key_t* master_key) {
+ if (secfalse == flash_otp_is_locked(FLASH_OTP_BLOCK_MASTER_KEY)) {
+ uint8_t rnd_bytes[SECRET_KEY_MASTER_KEY_SIZE];
+ if (!rng_fill_buffer_strong(rnd_bytes, SECRET_KEY_MASTER_KEY_SIZE)) {
+ memzero(rnd_bytes, sizeof(rnd_bytes));
+ return secfalse;
+ }
+ ensure(flash_otp_write(FLASH_OTP_BLOCK_MASTER_KEY, 0, rnd_bytes,
+ SECRET_KEY_MASTER_KEY_SIZE),
+ NULL);
+ ensure(flash_otp_lock(FLASH_OTP_BLOCK_MASTER_KEY), NULL);
+ }
+ ensure(flash_otp_read(FLASH_OTP_BLOCK_MASTER_KEY, 0, &master_key->bytes[0],
+ SECRET_KEY_MASTER_KEY_SIZE),
+ NULL);
+
+ master_key->size = SECRET_KEY_MASTER_KEY_SIZE;
+ return sectrue;
+}
+
+#endif // SECURE_MODE
diff --git a/core/embed/sec/secret/stm32u5/secret_keys.c b/core/embed/sec/secret/stm32u5/secret_keys.c
index a766f5a9..5a9a37a0 100644
--- a/core/embed/sec/secret/stm32u5/secret_keys.c
+++ b/core/embed/sec/secret/stm32u5/secret_keys.c
@@ -25,83 +25,11 @@
#include <sec/secret.h>
#include <sec/secret_keys.h>
-
-#ifdef SECRET_PRIVILEGED_MASTER_KEY_SLOT
-
+#include "../secret_keys_common.h"
#include "hmac.h"
#include "memzero.h"
-#include "nist256p1.h"
-
-// Key derivation indices
-#define KEY_INDEX_MCU_DEVICE_AUTH 0
-#define KEY_INDEX_OPTIGA_PAIRING 1
-#define KEY_INDEX_OPTIGA_MASKING 2
-#define KEY_INDEX_TROPIC_PAIRING_UNPRIVILEGED 3
-#define KEY_INDEX_TROPIC_PAIRING_PRIVILEGED 4
-#define KEY_INDEX_TROPIC_MASKING 5
-#define KEY_INDEX_NRF_PAIRING 6
-#define KEY_INDEX_STORAGE_SALT 7
-
-static secbool secret_key_derive_sym(uint8_t slot, uint16_t index,
- uint16_t subindex,
- uint8_t dest[SHA256_DIGEST_LENGTH]) {
- secbool ret = sectrue;
-
- // The diversifier consists of:
- // - the key derivation index (2 bytes big-endian), which identifies the
- // purpose of the key,
- // - the subindex (2 bytes big-endian), which is incremented until the derived
- // key meets required criteria, and
- // - the block index (1 byte), which can be used to produce outputs that are
- // longer than 32 bytes.
- uint8_t diversifier[] = {index >> 8, index & 0xff, subindex >> 8,
- subindex & 0xff, 0};
-
- uint8_t master_key[32] = {0};
- ret = secret_key_get(slot, master_key, sizeof(master_key));
- if (ret != sectrue) {
- goto cleanup;
- }
-
- hmac_sha256(master_key, sizeof(master_key), diversifier, sizeof(diversifier),
- dest);
-
-cleanup:
- memzero(master_key, sizeof(master_key));
- return ret;
-}
-
-#if defined(USE_OPTIGA) || defined(USE_TROPIC)
-static secbool secret_key_derive_nist256p1(
- uint8_t slot, uint16_t index, uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]) {
- _Static_assert(ECDSA_PRIVATE_KEY_SIZE == SHA256_DIGEST_LENGTH);
-
- secbool ret = sectrue;
- bignum256 s = {0};
- for (uint16_t i = 0; i < 10000; i++) {
- ret = secret_key_derive_sym(slot, index, i, dest);
- if (ret != sectrue) {
- goto cleanup;
- }
-
- bn_read_be(dest, &s);
- if (!bn_is_zero(&s) && bn_is_less(&s, &nist256p1.order)) {
- // Valid private key, we are done.
- ret = sectrue;
- goto cleanup;
- }
-
- // Invalid private key, we generate the next key in line.
- }
-
- // Loop exhausted all attempts without producing a valid private key.
- ret = secfalse;
-cleanup:
- memzero(&s, sizeof(s));
- return ret;
-}
-#endif
+#ifdef SECRET_PRIVILEGED_MASTER_KEY_SLOT
secbool secret_key_mcu_device_auth(uint8_t dest[MLDSA_SEEDBYTES]) {
_Static_assert(MLDSA_SEEDBYTES == SHA256_DIGEST_LENGTH);
@@ -120,6 +48,12 @@ secbool secret_key_optiga_masking(uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]) {
return secret_key_derive_nist256p1(SECRET_PRIVILEGED_MASTER_KEY_SLOT,
KEY_INDEX_OPTIGA_MASKING, dest);
}
+
+secbool secret_key_delegated_identity(uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]) {
+ return secret_key_derive_nist256p1(SECRET_UNPRIVILEGED_MASTER_KEY_SLOT,
+ KEY_INDEX_DELEGATED_IDENTITY, dest);
+}
+
#endif // USE_OPTIGA
#ifdef USE_TROPIC
@@ -160,9 +94,9 @@ secbool secret_key_tropic_masking(uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]) {
#ifdef USE_NRF_AUTH
-static secbool secequal(const void *ptr1, const void *ptr2, size_t n) {
- const uint8_t *p1 = ptr1;
- const uint8_t *p2 = ptr2;
+static secbool secequal(const void* ptr1, const void* ptr2, size_t n) {
+ const uint8_t* p1 = ptr1;
+ const uint8_t* p2 = ptr2;
uint8_t diff = 0;
size_t i = 0;
for (i = 0; i < n; ++i) {
@@ -184,8 +118,8 @@ secbool secret_key_nrf_pairing(uint8_t dest[NRF_PAIRING_SECRET_SIZE]) {
KEY_INDEX_NRF_PAIRING, 0, dest);
}
-secbool secret_validate_nrf_pairing(const uint8_t *message, size_t msg_len,
- const uint8_t *mac, size_t mac_len) {
+secbool secret_validate_nrf_pairing(const uint8_t* message, size_t msg_len,
+ const uint8_t* mac, size_t mac_len) {
secbool result = secfalse;
uint8_t key[NRF_PAIRING_SECRET_SIZE] = {0};
@@ -213,7 +147,7 @@ cleanup:
return result;
}
-#endif
+#endif // USE_NRF_AUTH
secbool secret_key_storage_salt(uint16_t fw_type,
uint8_t dest[SECRET_KEY_STORAGE_SALT_SIZE]) {
@@ -223,6 +157,9 @@ secbool secret_key_storage_salt(uint16_t fw_type,
}
#else // SECRET_PRIVILEGED_MASTER_KEY_SLOT
+#include <sec/rng.h>
+#include <sys/mpu.h>
+#include <util/flash_otp.h>
#ifdef USE_OPTIGA
secbool secret_key_optiga_pairing(uint8_t dest[OPTIGA_PAIRING_SECRET_SIZE]) {
@@ -230,6 +167,30 @@ secbool secret_key_optiga_pairing(uint8_t dest[OPTIGA_PAIRING_SECRET_SIZE]) {
}
#endif // USE_OPTIGA
+secbool secret_key_master_key_get(secret_key_master_key_t* master_key) {
+ if (secfalse == flash_otp_is_locked(FLASH_OTP_BLOCK_MASTER_KEY)) {
+ uint8_t rnd_bytes[SECRET_KEY_MASTER_KEY_SIZE];
+ if (!rng_fill_buffer_strong(rnd_bytes, SECRET_KEY_MASTER_KEY_SIZE)) {
+ memzero(rnd_bytes, sizeof(rnd_bytes));
+ return secfalse;
+ }
+ ensure(flash_otp_write(FLASH_OTP_BLOCK_MASTER_KEY, 0, rnd_bytes,
+ SECRET_KEY_MASTER_KEY_SIZE),
+ NULL);
+ }
+ ensure(flash_otp_read(FLASH_OTP_BLOCK_MASTER_KEY, 0, &master_key->bytes[0],
+ SECRET_KEY_MASTER_KEY_SIZE),
+ NULL);
+
+ master_key->size = SECRET_KEY_MASTER_KEY_SIZE;
+ return sectrue;
+}
+
+secbool secret_key_delegated_identity(uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]) {
+ return secret_key_derive_nist256p1(UNUSED_KEY_SLOT,
+ KEY_INDEX_DELEGATED_IDENTITY, dest);
+}
+
#endif // SECRET_PRIVILEGED_MASTER_KEY_SLOT
#endif // SECURE_MODE
diff --git a/core/embed/sec/secret/unix/secret_keys.c b/core/embed/sec/secret/unix/secret_keys.c
index 145f082e..86ceaacb 100644
--- a/core/embed/sec/secret/unix/secret_keys.c
+++ b/core/embed/sec/secret/unix/secret_keys.c
@@ -25,6 +25,7 @@
#include <sec/secret.h>
#include <sec/secret_keys.h>
+#include "../secret_keys_common.h"
#ifdef USE_TROPIC
@@ -70,6 +71,22 @@ secbool secret_key_tropic_masking(uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]) {
return sectrue;
}
-#endif
+#endif // USE_TROPIC
+secbool secret_key_delegated_identity(uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]) {
+#ifdef SECRET_UNPRIVILEGED_MASTER_KEY_SLOT
+ static uint8_t key_slot = SECRET_UNPRIVILEGED_MASTER_KEY_SLOT;
+#else
+ static uint8_t key_slot = UNUSED_KEY_SLOT;
#endif
+ return secret_key_derive_nist256p1(key_slot, KEY_INDEX_DELEGATED_IDENTITY,
+ dest);
+}
+
+secbool secret_key_master_key_get(secret_key_master_key_t* master_key) {
+ memset(master_key->bytes, 0, SECRET_KEY_MASTER_KEY_SIZE);
+ master_key->size = SECRET_KEY_MASTER_KEY_SIZE;
+ return sectrue;
+}
+
+#endif // SECURE_MODE
diff --git a/core/embed/sys/smcall/stm32/smcall_dispatch.c b/core/embed/sys/smcall/stm32/smcall_dispatch.c
index 4ce8254a..d02ea5fb 100644
--- a/core/embed/sys/smcall/stm32/smcall_dispatch.c
+++ b/core/embed/sys/smcall/stm32/smcall_dispatch.c
@@ -24,6 +24,7 @@
#include <sec/random_delays.h>
#include <sec/rng.h>
#include <sec/secret.h>
+#include <sec/secret_keys.h>
#include <sys/bootargs.h>
#include <sys/bootutils.h>
#include <sys/irq.h>
@@ -196,7 +197,12 @@ __attribute((no_stack_protector)) void smcall_handler(uint32_t *args,
optiga_set_sec_max();
} break;
#endif
-#endif
+#endif // USE_OPTIGA
+
+ case SMCALL_SECRET_KEYS_GET_DELEGATED_IDENTITY_KEY: {
+ uint8_t *dest = (uint8_t *)args[0];
+ args[0] = secret_key_delegated_identity__verified(dest);
+ } break;
case SMCALL_STORAGE_SETUP: {
PIN_UI_WAIT_CALLBACK callback = (PIN_UI_WAIT_CALLBACK)args[0];
@@ -382,7 +388,7 @@ __attribute((no_stack_protector)) void smcall_handler(uint32_t *args,
size_t data_size = (size_t)args[3];
args[0] = backup_ram_write__verified(key, type, data, data_size);
} break;
-#endif
+#endif // USE_BACKUP_RAM
default:
system_exit_fatal("Invalid smcall", __FILE__, __LINE__);
diff --git a/core/embed/sys/smcall/stm32/smcall_numbers.h b/core/embed/sys/smcall/stm32/smcall_numbers.h
index bbfda6be..a264f5f9 100644
--- a/core/embed/sys/smcall/stm32/smcall_numbers.h
+++ b/core/embed/sys/smcall/stm32/smcall_numbers.h
@@ -99,4 +99,6 @@ typedef enum {
SMCALL_BACKUP_RAM_READ,
SMCALL_BACKUP_RAM_WRITE,
+ SMCALL_SECRET_KEYS_GET_DELEGATED_IDENTITY_KEY,
+
} smcall_number_t;
diff --git a/core/embed/sys/smcall/stm32/smcall_stubs.c b/core/embed/sys/smcall/stm32/smcall_stubs.c
index 425bf1e6..8c56aaff 100644
--- a/core/embed/sys/smcall/stm32/smcall_stubs.c
+++ b/core/embed/sys/smcall/stm32/smcall_stubs.c
@@ -195,13 +195,24 @@ bool optiga_read_sec(uint8_t *sec) {
return (bool)smcall_invoke1((uint32_t)sec, SMCALL_OPTIGA_READ_SEC);
}
+#endif // USE_OPTIGA
+
+// =============================================================================
+// secret_keys.h
+// =============================================================================
+
+#include <sec/secret_keys.h>
+
+secbool secret_key_delegated_identity(uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]) {
+ return (secbool)smcall_invoke1((uint32_t)dest,
+ SMCALL_SECRET_KEYS_GET_DELEGATED_IDENTITY_KEY);
+}
+
#if PYOPT == 0
void optiga_set_sec_max(void) { smcall_invoke0(SMCALL_OPTIGA_SET_SEC_MAX); }
#endif
-#endif // USE_OPTIGA
-
// =============================================================================
// storage.h
// =============================================================================
diff --git a/core/embed/sys/smcall/stm32/smcall_verifiers.c b/core/embed/sys/smcall/stm32/smcall_verifiers.c
index f3b239d8..ffc21bba 100644
--- a/core/embed/sys/smcall/stm32/smcall_verifiers.c
+++ b/core/embed/sys/smcall/stm32/smcall_verifiers.c
@@ -217,6 +217,23 @@ access_violation:
// ---------------------------------------------------------------------
+#include <sec/secret_keys.h>
+
+secbool secret_key_delegated_identity__verified(
+ uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]) {
+ if (!probe_write_access(dest, ECDSA_PRIVATE_KEY_SIZE)) {
+ goto access_violation;
+ }
+
+ return secret_key_delegated_identity(dest);
+
+access_violation:
+ apptask_access_violation();
+ return secfalse;
+}
+
+// ---------------------------------------------------------------------
+
typedef __attribute__((cmse_nonsecure_call))
PIN_UI_WAIT_CALLBACK ns_storage_callback_t;
diff --git a/core/embed/sys/smcall/stm32/smcall_verifiers.h b/core/embed/sys/smcall/stm32/smcall_verifiers.h
index 75d72828..eca1c646 100644
--- a/core/embed/sys/smcall/stm32/smcall_verifiers.h
+++ b/core/embed/sys/smcall/stm32/smcall_verifiers.h
@@ -72,6 +72,14 @@ bool __wur optiga_read_sec__verified(uint8_t *sec);
#endif // USE_OPTIGA
// ---------------------------------------------------------------------
+
+#include <sec/secret_keys.h>
+
+secbool secret_key_delegated_identity__verified(
+ uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]);
+
+// ---------------------------------------------------------------------
+
#include <sec/storage.h>
void storage_setup__verified(PIN_UI_WAIT_CALLBACK callback);
@@ -155,4 +163,5 @@ secbool secret_validate_nrf_pairing__verified(const uint8_t *message,
const uint8_t *mac,
size_t mac_len);
#endif
+
#endif // SECMON
diff --git a/core/embed/sys/syscall/inc/sys/syscall_numbers.h b/core/embed/sys/syscall/inc/sys/syscall_numbers.h
index 1564fc00..f6b326d0 100644
--- a/core/embed/sys/syscall/inc/sys/syscall_numbers.h
+++ b/core/embed/sys/syscall/inc/sys/syscall_numbers.h
@@ -178,6 +178,8 @@ typedef enum {
SYSCALL_STORAGE_GET,
+ SYSCALL_SECRET_KEYS_GET_DELEGATED_IDENTITY_KEY,
+
// ------------------------------------------------------
// Following syscalls are executed in kernel thread mode
diff --git a/core/embed/sys/syscall/stm32/syscall_dispatch.c b/core/embed/sys/syscall/stm32/syscall_dispatch.c
index 44ea46de..62963537 100644
--- a/core/embed/sys/syscall/stm32/syscall_dispatch.c
+++ b/core/embed/sys/syscall/stm32/syscall_dispatch.c
@@ -28,6 +28,7 @@
#include <io/usb.h>
#include <sec/rng.h>
#include <sec/secret.h>
+#include <sec/secret_keys.h>
#include <sys/bootutils.h>
#include <sys/irq.h>
#include <sys/notify.h>
@@ -438,7 +439,12 @@ __attribute((no_stack_protector)) void syscall_handler(uint32_t *args,
optiga_set_sec_max();
} break;
#endif
-#endif
+#endif // USE_OPTIGA
+
+ case SYSCALL_SECRET_KEYS_GET_DELEGATED_IDENTITY_KEY: {
+ uint8_t *dest = (uint8_t *)args[0];
+ args[0] = secret_key_delegated_identity__verified(dest);
+ } break;
case SYSCALL_STORAGE_SETUP: {
PIN_UI_WAIT_CALLBACK callback = (PIN_UI_WAIT_CALLBACK)args[0];
diff --git a/core/embed/sys/syscall/stm32/syscall_stubs.c b/core/embed/sys/syscall/stm32/syscall_stubs.c
index 86848de6..c19c51c5 100644
--- a/core/embed/sys/syscall/stm32/syscall_stubs.c
+++ b/core/embed/sys/syscall/stm32/syscall_stubs.c
@@ -427,6 +427,17 @@ void optiga_set_sec_max(void) { syscall_invoke0(SYSCALL_OPTIGA_SET_SEC_MAX); }
#endif // USE_OPTIGA
+// =============================================================================
+// secret_keys.h
+// =============================================================================
+
+#include <sec/secret_keys.h>
+
+secbool secret_key_delegated_identity(uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]) {
+ return (secbool)syscall_invoke1(
+ (uint32_t)dest, SYSCALL_SECRET_KEYS_GET_DELEGATED_IDENTITY_KEY);
+}
+
// =============================================================================
// storage.h
// =============================================================================
diff --git a/core/embed/sys/syscall/stm32/syscall_verifiers.c b/core/embed/sys/syscall/stm32/syscall_verifiers.c
index af8e3932..70857d50 100644
--- a/core/embed/sys/syscall/stm32/syscall_verifiers.c
+++ b/core/embed/sys/syscall/stm32/syscall_verifiers.c
@@ -479,6 +479,23 @@ access_violation:
// ---------------------------------------------------------------------
+#include <sec/secret_keys.h>
+
+secbool secret_key_delegated_identity__verified(
+ uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]) {
+ if (!probe_write_access(dest, ECDSA_PRIVATE_KEY_SIZE)) {
+ goto access_violation;
+ }
+
+ return secret_key_delegated_identity(dest);
+
+access_violation:
+ apptask_access_violation();
+ return secfalse;
+}
+
+// ---------------------------------------------------------------------
+
static PIN_UI_WAIT_CALLBACK storage_callback = NULL;
static secbool storage_callback_wrapper(uint32_t wait, uint32_t progress,
diff --git a/core/embed/sys/syscall/stm32/syscall_verifiers.h b/core/embed/sys/syscall/stm32/syscall_verifiers.h
index 0c458c23..350828b4 100644
--- a/core/embed/sys/syscall/stm32/syscall_verifiers.h
+++ b/core/embed/sys/syscall/stm32/syscall_verifiers.h
@@ -129,6 +129,12 @@ bool __wur optiga_read_sec__verified(uint8_t *sec);
#endif // USE_OPTIGA
+// ---------------------------------------------------------------------
+#include <sec/secret_keys.h>
+
+secbool secret_key_delegated_identity__verified(
+ uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]);
+
// ---------------------------------------------------------------------
#include <sec/storage.h>
diff --git a/core/embed/upymod/modtrezorutils/modtrezorutils.c b/core/embed/upymod/modtrezorutils/modtrezorutils.c
index c74b2b17..f9caa5df 100644
--- a/core/embed/upymod/modtrezorutils/modtrezorutils.c
+++ b/core/embed/upymod/modtrezorutils/modtrezorutils.c
@@ -35,6 +35,7 @@
#include "embed/upymod/trezorobj.h"
#include <io/usb.h>
+#include <sec/secret_keys.h>
#include <sys/bootutils.h>
#include <sys/notify.h>
#include <util/fwutils.h>
@@ -234,6 +235,22 @@ STATIC mp_obj_t mod_trezorutils_firmware_vendor(void) {
STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorutils_firmware_vendor_obj,
mod_trezorutils_firmware_vendor);
+/// def delegated_identity() -> bytes:
+/// """
+/// Returns the delegated identity key used for registration and space
+/// management at Evolu.
+/// """
+STATIC mp_obj_t mod_trezorutils_delegated_identity(void) {
+ uint8_t private_key[ECDSA_PRIVATE_KEY_SIZE] = {0};
+ if (secret_key_delegated_identity(private_key) != sectrue) {
+ mp_raise_msg(&mp_type_RuntimeError,
+ MP_ERROR_TEXT("Failed to read delegated identity."));
+ }
+ return mp_obj_new_bytes(private_key, sizeof(private_key));
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorutils_delegated_identity_obj,
+ mod_trezorutils_delegated_identity);
+
/// def unit_color() -> int | None:
/// """
/// Returns the color of the unit.
@@ -583,7 +600,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorutils_check_firmware_header_obj,
/// def bootloader_locked() -> bool | None:
/// """
-/// Returns True/False if the the bootloader is locked/unlocked and None if
+/// Returns True/False if the bootloader is locked/unlocked and None if
/// the feature is not supported.
/// """
STATIC mp_obj_t mod_trezorutils_bootloader_locked() {
@@ -779,7 +796,8 @@ STATIC const mp_rom_map_elem_t mp_module_trezorutils_globals_table[] = {
{MP_ROM_QSTR(MP_QSTR_nrf_get_version),
MP_ROM_PTR(&mod_trezorutils_nrf_get_version_obj)},
#endif
-
+ {MP_ROM_QSTR(MP_QSTR_delegated_identity),
+ MP_ROM_PTR(&mod_trezorutils_delegated_identity_obj)},
{MP_ROM_QSTR(MP_QSTR_unit_color),
MP_ROM_PTR(&mod_trezorutils_unit_color_obj)},
{MP_ROM_QSTR(MP_QSTR_unit_packaging),
diff --git a/core/embed/upymod/qstrdefsport.h b/core/embed/upymod/qstrdefsport.h
index 80234347..a88eb255 100644
--- a/core/embed/upymod/qstrdefsport.h
+++ b/core/embed/upymod/qstrdefsport.h
@@ -102,6 +102,7 @@ Q(apps.common.backup)
Q(apps.common.backup_types)
Q(apps.common.cache)
Q(apps.common.cbor)
+Q(apps.common.certificates)
Q(apps.common.chunked)
Q(apps.common.coininfo)
Q(apps.common.coins)
@@ -119,6 +120,11 @@ Q(apps.common.sdcard)
Q(apps.common.seed)
Q(apps.common.signverify)
Q(apps.common.writers)
+Q(apps.evolu)
+Q(apps.evolu.common)
+Q(apps.evolu.get_delegated_identity_key)
+Q(apps.evolu.get_node)
+Q(apps.evolu.sign_registration_request)
Q(apps.homescreen)
Q(apps.homescreen.device_menu)
Q(apps.management)
@@ -152,7 +158,6 @@ Q(apps.misc)
Q(apps.misc.cipher_key_value)
Q(apps.misc.get_ecdh_session_key)
Q(apps.misc.get_entropy)
-Q(apps.misc.get_evolu_node)
Q(apps.misc.get_firmware_hash)
Q(apps.misc.payment_notification)
Q(apps.misc.sign_identity)
@@ -185,6 +190,7 @@ Q(cache_common)
Q(caesar)
Q(cashaddr)
Q(cbor)
+Q(certificates)
Q(change_detector)
Q(change_language)
Q(change_pin)
@@ -213,14 +219,16 @@ Q(device_menu)
Q(eckhart)
Q(enums)
Q(errors)
+Q(evolu)
Q(fido)
Q(fido2)
Q(get_address)
+Q(get_delegated_identity_key)
Q(get_ecdh_session_key)
Q(get_entropy)
-Q(get_evolu_node)
Q(get_firmware_hash)
Q(get_next_u2f_counter)
+Q(get_node)
Q(get_nonce)
Q(get_ownership_id)
Q(get_ownership_proof)
@@ -287,6 +295,7 @@ Q(sig_hasher)
Q(sign_event)
Q(sign_identity)
Q(sign_message)
+Q(sign_registration_request)
Q(sign_tx)
Q(signverify)
Q(slip39)
diff --git a/core/mocks/generated/trezorutils.pyi b/core/mocks/generated/trezorutils.pyi
index 12f33f27..7076190e 100644
--- a/core/mocks/generated/trezorutils.pyi
+++ b/core/mocks/generated/trezorutils.pyi
@@ -70,6 +70,14 @@ def firmware_vendor() -> str:
"""
+# upymod/modtrezorutils/modtrezorutils.c
+def delegated_identity() -> bytes:
+ """
+ Returns the delegated identity key used for registration and space
+ management at Evolu.
+ """
+
+
# upymod/modtrezorutils/modtrezorutils.c
def unit_color() -> int | None:
"""
@@ -196,7 +204,7 @@ def check_firmware_header(header : AnyBytes) -> FirmwareHeaderInfo:
# upymod/modtrezorutils/modtrezorutils.c
def bootloader_locked() -> bool | None:
"""
- Returns True/False if the the bootloader is locked/unlocked and None if
+ Returns True/False if the bootloader is locked/unlocked and None if
the feature is not supported.
"""
diff --git a/core/mocks/trezortranslate_keys.pyi b/core/mocks/trezortranslate_keys.pyi
index 096648a4..9db00d29 100644
--- a/core/mocks/trezortranslate_keys.pyi
+++ b/core/mocks/trezortranslate_keys.pyi
@@ -813,6 +813,9 @@ class TR:
sd_card__use_different_card: str = "Use a different card or format the SD card to the FAT32 filesystem."
sd_card__wanna_format: str = "Do you really want to format the SD card?"
sd_card__wrong_sd_card: str = "Wrong SD card."
+ secure_sync__delegated_identity_key_no_thp: str = "Allow Trezor Suite to use Suite Sync with this Trezor?"
+ secure_sync__delegated_identity_key_thp: str = "Allow {0} on {1} to use Suite Sync with this Trezor?"
+ secure_sync__header: str = "Suite Sync"
send__cancel_sign: str = "Cancel sign"
send__cancel_transaction: str = "Cancel transaction"
send__confirm_sending: str = "Sending amount"
diff --git a/core/site_scons/models/stm32f4_common.py b/core/site_scons/models/stm32f4_common.py
index 5d07a78c..355a0808 100644
--- a/core/site_scons/models/stm32f4_common.py
+++ b/core/site_scons/models/stm32f4_common.py
@@ -70,6 +70,7 @@ def stm32f4_common_files(env, features_wanted, defines, sources, paths):
"embed/sec/rng/rng_common.c",
"embed/sec/secret/stm32f4/secret.c",
"embed/sec/secret/stm32f4/secret_keys.c",
+ "embed/sec/secret/secret_keys_common.c",
"embed/sec/storage/stm32f4/storage_salt.c",
"embed/sec/time_estimate/stm32/time_estimate.c",
"embed/sys/irq/stm32/irq.c",
diff --git a/core/site_scons/models/stm32u5_common.py b/core/site_scons/models/stm32u5_common.py
index 88d8d7e5..ec51ed0b 100644
--- a/core/site_scons/models/stm32u5_common.py
+++ b/core/site_scons/models/stm32u5_common.py
@@ -89,6 +89,7 @@ def stm32u5_common_files(env, features_wanted, defines, sources, paths):
"embed/sec/rng/rng_common.c",
"embed/sec/secret/stm32u5/secret.c",
"embed/sec/secret/stm32u5/secret_keys.c",
+ "embed/sec/secret/secret_keys_common.c",
"embed/sec/secure_aes/stm32u5/secure_aes.c",
"embed/sec/secure_aes/stm32u5/secure_aes_unpriv.c",
"embed/sec/storage/stm32u5/storage_salt.c",
diff --git a/core/site_scons/models/unix_common.py b/core/site_scons/models/unix_common.py
index c5006fca..fa70171e 100644
--- a/core/site_scons/models/unix_common.py
+++ b/core/site_scons/models/unix_common.py
@@ -37,6 +37,7 @@ def unix_common_files(env, features_wanted, defines, sources, paths):
"embed/sec/random_delays/unix/random_delays.c",
"embed/sec/secret/unix/secret.c",
"embed/sec/secret/unix/secret_keys.c",
+ "embed/sec/secret/secret_keys_common.c",
"embed/sec/storage/unix/storage_salt.c",
"embed/sec/monoctr/unix/monoctr.c",
"embed/sec/rng/unix/rng.c",
diff --git a/core/src/apps/common/certificates.py b/core/src/apps/common/certificates.py
new file mode 100644
index 00000000..ec7a5acf
--- /dev/null
+++ b/core/src/apps/common/certificates.py
@@ -0,0 +1,23 @@
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from buffer_types import AnyBytes
+
+ from trezor.utils import BufferReader
+
+
+def parse_cert_chain(r: BufferReader) -> list[AnyBytes]:
+ from trezor import wire
+ from trezor.crypto.der import read_length
+
+ certificates = []
+ while r.remaining_count() > 0:
+ cert_begin = r.offset
+ if r.get() != 0x30:
+ raise wire.FirmwareError("Device certificate is corrupted.")
+ n = read_length(r)
+ cert_len = r.offset - cert_begin + n
+ r.seek(cert_begin)
+ certificates.append(r.read_memoryview(cert_len))
+
+ return certificates
diff --git a/core/src/apps/evolu/__init__.py b/core/src/apps/evolu/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/core/src/apps/evolu/common.py b/core/src/apps/evolu/common.py
new file mode 100644
index 00000000..b50decc9
--- /dev/null
+++ b/core/src/apps/evolu/common.py
@@ -0,0 +1,44 @@
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from buffer_types import AnyBytes
+ from typing import Sequence
+
+
+def check_delegated_identity_proof(
+ provided_proof: AnyBytes,
+ header: AnyBytes,
+ arguments: Sequence[AnyBytes] | None = None,
+) -> bool:
+ from trezorutils import delegated_identity
+
+ from trezor.crypto.curve import nist256p1
+ from trezor.crypto.hashlib import sha256
+ from trezor.utils import HashWriter
+
+ from apps.common.writers import write_compact_size
+
+ private_key = delegated_identity()
+ public_key = get_public_key_from_private_key(private_key)
+
+ hash_writer = HashWriter(sha256())
+ write_compact_size(hash_writer, len(header))
+ hash_writer.extend(header)
+
+ if arguments:
+ for arg in arguments:
+ write_compact_size(hash_writer, len(arg))
+ hash_writer.extend(arg)
+
+ return nist256p1.verify(
+ public_key,
+ provided_proof,
+ hash_writer.get_digest(),
+ )
+
+
+def get_public_key_from_private_key(private_key: AnyBytes) -> bytes:
+ from trezor.crypto.curve import nist256p1
+
+ public_key = nist256p1.publickey(private_key, False)
+ return public_key
diff --git a/core/src/apps/evolu/get_delegated_identity_key.py b/core/src/apps/evolu/get_delegated_identity_key.py
new file mode 100644
index 00000000..2bfb507e
--- /dev/null
+++ b/core/src/apps/evolu/get_delegated_identity_key.py
@@ -0,0 +1,78 @@
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from trezor.messages import EvoluDelegatedIdentityKey, EvoluGetDelegatedIdentityKey
+
+
+async def get_delegated_identity_key(
+ msg: EvoluGetDelegatedIdentityKey,
+) -> EvoluDelegatedIdentityKey:
+ """
+ Retrieves the delegated identity private key for this device.
+ This key is used
+
+ 1. to provide the identity of this device to the Quota Manager server.
+ 2. to authenticate the Suite to the Quota Manager server in future Suite Sync requests.
+ 3. as a token of the user's trust in this Trezor - Suite communication. Subsequent Suite Sync requests
+ to this Trezor will be authenticated using this key, so we can skip more user confirmations.
+
+ On devices with THP, we require a valid THP credential to be provided in the `msg`. The metadata from the credential
+ is then displayed to the user during the confirmation. On devices without THP a generic confirmation dialog is shown.
+
+ Args:
+ msg (EvoluGetDelegatedIdentityKey): The incoming request message containing parameters for the operation.
+ Returns:
+ EvoluDelegatedIdentityKey: The response message containing the delegated identity private key.
+ Raises:
+ ValueError: If THP is enabled but the credential is missing or invalid.
+ """
+
+ from trezorutils import delegated_identity
+
+ from trezor import utils
+ from trezor.messages import EvoluDelegatedIdentityKey
+
+ if utils.USE_THP:
+ await confirm_thp(msg)
+ else:
+ await confirm_no_thp()
+
+ private_key = delegated_identity()
+
+ return EvoluDelegatedIdentityKey(private_key=private_key)
+
+
+async def confirm_thp(msg: EvoluGetDelegatedIdentityKey) -> None:
+ from trezor import TR
+ from trezor.ui.layouts import confirm_action
+
+ from apps.thp.credential_manager import decode_credential, validate_credential
+
+ if msg.thp_credential is None:
+ raise ValueError("THP credentials must be provided when THP is enabled")
+ if msg.host_static_public_key is None:
+ raise ValueError("Host static public key must be provided when THP is enabled")
+
+ credential_received = decode_credential(msg.thp_credential)
+
+ if not validate_credential(credential_received, msg.host_static_public_key):
+ raise ValueError("Invalid credential")
+
+ app_name = credential_received.cred_metadata.app_name
+ host_name = credential_received.cred_metadata.host_name
+ await confirm_action(
+ "secure_sync",
+ TR.secure_sync__header,
+ TR.secure_sync__delegated_identity_key_thp.format(app_name, host_name),
+ )
+
+
+async def confirm_no_thp() -> None:
+ from trezor import TR
+ from trezor.ui.layouts import confirm_action
+
+ await confirm_action(
+ "secure_sync",
+ TR.secure_sync__header,
+ TR.secure_sync__delegated_identity_key_no_thp,
+ )
diff --git a/core/src/apps/evolu/get_node.py b/core/src/apps/evolu/get_node.py
new file mode 100644
index 00000000..09529ec4
--- /dev/null
+++ b/core/src/apps/evolu/get_node.py
@@ -0,0 +1,51 @@
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from trezor.messages import EvoluGetNode, EvoluNode
+
+_EVOLU_KEY_PATH_PREFIX = [b"TREZOR", b"Evolu"]
+
+
+async def get_node(msg: EvoluGetNode) -> EvoluNode:
+ """
+ Returns the SLIP-21 node to generate Evolu keys for this passphrase.
+
+ This function does not work if the device is not initialized.
+
+ This function requires a proof of delegated identity.
+
+ Args:
+ msg (EvoluGetNode): The message containing parameters and proof of delegated identity.
+ Returns:
+ EvoluNode: The derived SLIP-21 node containing the necessary data for further key generation.
+ Raises:
+ NotInitialized: If the device is not initialized.
+ ValueError: If the proof of delegated identity is missing or invalid.
+ """
+ from storage.device import is_initialized
+ from trezor.messages import EvoluNode
+ from trezor.wire import NotInitialized
+
+ from .common import check_delegated_identity_proof
+
+ if not is_initialized():
+ raise NotInitialized("Device is not initialized")
+
+ if not check_delegated_identity_proof(
+ bytes(msg.proof_of_delegated_identity), header=b"EvoluGetNode"
+ ):
+ raise ValueError("Invalid proof")
+
+ # TODO: adjust copy when the usage is exposed via Trezor Suite
+
+ return EvoluNode(data=await derive_evolu_node())
+
+
+async def derive_evolu_node() -> bytes:
+ from apps.common.seed import Slip21Node, get_seed
+
+ seed = await get_seed()
+ node = Slip21Node(seed)
+ node.derive_path(_EVOLU_KEY_PATH_PREFIX)
+
+ return node.data
diff --git a/core/src/apps/evolu/sign_registration_request.py b/core/src/apps/evolu/sign_registration_request.py
new file mode 100644
index 00000000..003854b6
--- /dev/null
+++ b/core/src/apps/evolu/sign_registration_request.py
@@ -0,0 +1,115 @@
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from buffer_types import AnyBytes
+
+ from trezor.messages import EvoluRegistrationRequest, EvoluSignRegistrationRequest
+
+BYTES_IN_UINT32 = 4
+
+
+async def sign_registration_request(
+ msg: EvoluSignRegistrationRequest,
+) -> EvoluRegistrationRequest:
+ """
+ Signs a registration request for this device to register `msg.size_to_acquire` bytes of space on the Quota Manager server.
+ The request is signed using the device's Optiga certificate.
+
+ This function only works if the bootloader is locked and if the device has Optiga available.
+
+ We require a proof of delegated identity to be provided. It proves that the `delegated_identity_key`
+ has already been issued to this Suite.
+
+ Returns the signature and the Optiga's certificate chain which are to be sent to the Quota Manager server during the registration request.
+
+ Args:
+ msg (EvoluSignRegistrationRequest): The protobuf message containing the proof of delegated identity,
+ the challenge from the Quota Manager server, and the size to acquire.
+ Returns:
+ EvoluRegistrationRequest: The signature of the registration request and the Optiga's certificate chain.
+ Raises:
+ wire.ProcessError: If the bootloader is unlocked or signing is inaccessible.
+ RuntimeError: If Optiga is not available.
+ ValueError: If the delegated identity proof is invalid.
+
+ """
+ from trezor import utils
+ from trezor.messages import EvoluRegistrationRequest
+ from trezor.utils import BufferReader
+
+ from apps.common.certificates import parse_cert_chain
+ from apps.evolu.common import check_delegated_identity_proof
+
+ if utils.USE_OPTIGA:
+ from trezor.crypto import optiga
+ else:
+ raise RuntimeError("Optiga is not available")
+
+ challenge_bytes, size_bytes = _check_data(
+ msg.challenge_from_server, msg.size_to_acquire
+ )
+
+ if not check_delegated_identity_proof(
+ provided_proof=msg.proof_of_delegated_identity,
+ header=b"EvoluSignRegistrationRequest",
+ arguments=[
+ challenge_bytes,
+ size_bytes,
+ ],
+ ):
+ raise ValueError("Invalid proof")
+
+ signature = _get_signature(challenge_bytes, size_bytes)
+ r = BufferReader(optiga.get_certificate(optiga.DEVICE_CERT_INDEX))
+ certificates = parse_cert_chain(r)
+
+ return EvoluRegistrationRequest(
+ certificate_chain=certificates,
+ signature=signature,
+ )
+
+
+def _check_data(challenge: AnyBytes, size: int) -> tuple[AnyBytes, bytes]:
+ from trezor import wire
+
+ if not 1 <= len(challenge) <= 255:
+ raise wire.DataError("Invalid challenge length")
+ if not 0 <= size <= 0xFFFFFFFF:
+ raise wire.DataError("Invalid size_to_acquire")
+
+ size_to_acquire_bytes = size.to_bytes(BYTES_IN_UINT32, "big")
+
+ return challenge, size_to_acquire_bytes
+
+
+def _get_signature(challenge_bytes: AnyBytes, size_bytes: bytes) -> bytes:
+ from trezorutils import delegated_identity
+
+ from trezor import utils, wire
+ from trezor.crypto import optiga
+ from trezor.crypto.hashlib import sha256
+
+ from apps.common.writers import write_compact_size
+
+ from .common import get_public_key_from_private_key
+
+ private_key = delegated_identity()
+ public_key = get_public_key_from_private_key(private_key)
+
+ header = b"EvoluSignRegistrationRequestV1:"
+ components = [
+ header,
+ public_key,
+ challenge_bytes,
+ size_bytes,
+ ]
+ hash_writer = utils.HashWriter(sha256())
+ for component in components:
+ write_compact_size(hash_writer, len(component))
+ hash_writer.extend(component)
+
+ try:
+ signature = optiga.sign(optiga.DEVICE_ECC_KEY_INDEX, hash_writer.get_digest())
+ except optiga.SigningInaccessible:
+ raise wire.ProcessError("Signing inaccessible.")
+ return signature
diff --git a/core/src/apps/management/authenticate_device.py b/core/src/apps/management/authenticate_device.py
index 81b506c2..67ebd619 100644
--- a/core/src/apps/management/authenticate_device.py
+++ b/core/src/apps/management/authenticate_device.py
@@ -1,27 +1,7 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
- from buffer_types import AnyBytes
-
from trezor.messages import AuthenticateDevice, AuthenticityProof
- from trezor.utils import BufferReader
-
-
-def parse_cert_chain(r: BufferReader) -> list[AnyBytes]:
- from trezor import wire
- from trezor.crypto.der import read_length
-
- certificates = []
- while r.remaining_count() > 0:
- cert_begin = r.offset
- if r.get() != 0x30:
- raise wire.FirmwareError("Device certificate is corrupted.")
- n = read_length(r)
- cert_len = r.offset - cert_begin + n
- r.seek(cert_begin)
- certificates.append(r.read_memoryview(cert_len))
-
- return certificates
async def authenticate_device(msg: AuthenticateDevice) -> AuthenticityProof:
@@ -34,6 +14,7 @@ async def authenticate_device(msg: AuthenticateDevice) -> AuthenticityProof:
from trezor.ui.layouts.progress import progress
from trezor.utils import BufferReader, bootloader_locked
+ from apps.common.certificates import parse_cert_chain
from apps.common.writers import write_compact_size
if not bootloader_locked():
diff --git a/core/src/apps/misc/get_evolu_node.py b/core/src/apps/misc/get_evolu_node.py
deleted file mode 100644
index 3df5bb32..00000000
--- a/core/src/apps/misc/get_evolu_node.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from typing import TYPE_CHECKING
-
-if TYPE_CHECKING:
- from trezor.messages import EvoluGetNode, EvoluNode
-
-_EVOLU_KEY_PATH_PREFIX = [b"TREZOR", b"Evolu"]
-
-
-async def get_evolu_node(_msg: EvoluGetNode) -> EvoluNode:
- from storage.device import is_initialized
- from trezor.messages import EvoluNode
- from trezor.ui.layouts import confirm_action
- from trezor.wire import NotInitialized
-
- from apps.common.seed import Slip21Node, get_seed
-
- if not is_initialized():
- raise NotInitialized("Device is not initialized")
-
- # TODO: adjust copy when the usage is exposed via Trezor Suite
- await confirm_action(
- "get_evolu_keys",
- "Evolu node",
- action="Derive SLIP-21 node for Evolu?",
- prompt_screen=True,
- )
- seed = await get_seed()
- node = Slip21Node(seed)
- node.derive_path(_EVOLU_KEY_PATH_PREFIX)
-
- return EvoluNode(data=node.data)
diff --git a/core/src/apps/workflow_handlers.py b/core/src/apps/workflow_handlers.py
index 3a120bd6..e8609f3f 100644
--- a/core/src/apps/workflow_handlers.py
+++ b/core/src/apps/workflow_handlers.py
@@ -106,8 +106,14 @@ def _find_message_handler_module(msg_type: int) -> str:
return "apps.misc.cipher_key_value"
if msg_type == MessageType.GetFirmwareHash:
return "apps.misc.get_firmware_hash"
+
+ # evolu
if msg_type == MessageType.EvoluGetNode:
- return "apps.misc.get_evolu_node"
+ return "apps.evolu.get_node"
+ if msg_type == MessageType.EvoluSignRegistrationRequest:
+ return "apps.evolu.sign_registration_request"
+ if msg_type == MessageType.EvoluGetDelegatedIdentityKey:
+ return "apps.evolu.get_delegated_identity_key"
if not utils.BITCOIN_ONLY:
# When promoting the Nostr app to production-level
diff --git a/core/src/trezor/enums/MessageType.py b/core/src/trezor/enums/MessageType.py
index efebd81f..2a4d7a9f 100644
--- a/core/src/trezor/enums/MessageType.py
+++ b/core/src/trezor/enums/MessageType.py
@@ -88,6 +88,10 @@ ECDHSessionKey = 62
PaymentNotification = 52
EvoluGetNode = 2100
EvoluNode = 2101
+EvoluSignRegistrationRequest = 2102
+EvoluRegistrationRequest = 2103
+EvoluGetDelegatedIdentityKey = 2104
+EvoluDelegatedIdentityKey = 2105
BenchmarkListNames = 9100
BenchmarkNames = 9101
BenchmarkRun = 9102
diff --git a/core/src/trezor/enums/__init__.py b/core/src/trezor/enums/__init__.py
index b0084c7b..77ce6af8 100644
--- a/core/src/trezor/enums/__init__.py
+++ b/core/src/trezor/enums/__init__.py
@@ -625,6 +625,10 @@ if TYPE_CHECKING:
NostrEventSignature = 2004
EvoluGetNode = 2100
EvoluNode = 2101
+ EvoluSignRegistrationRequest = 2102
+ EvoluRegistrationRequest = 2103
+ EvoluGetDelegatedIdentityKey = 2104
+ EvoluDelegatedIdentityKey = 2105
BenchmarkListNames = 9100
BenchmarkNames = 9101
BenchmarkRun = 9102
diff --git a/core/src/trezor/messages.py b/core/src/trezor/messages.py
index 0354c5af..b840c599 100644
--- a/core/src/trezor/messages.py
+++ b/core/src/trezor/messages.py
@@ -4104,6 +4104,14 @@ if TYPE_CHECKING:
return isinstance(msg, cls)
class EvoluGetNode(protobuf.MessageType):
+ proof_of_delegated_identity: "AnyBytes"
+
+ def __init__(
+ self,
+ *,
+ proof_of_delegated_identity: "AnyBytes",
+ ) -> None:
+ pass
@classmethod
def is_type_of(cls, msg: Any) -> TypeGuard["EvoluGetNode"]:
@@ -4123,6 +4131,70 @@ if TYPE_CHECKING:
def is_type_of(cls, msg: Any) -> TypeGuard["EvoluNode"]:
return isinstance(msg, cls)
+ class EvoluSignRegistrationRequest(protobuf.MessageType):
+ challenge_from_server: "AnyBytes"
+ size_to_acquire: "int"
+ proof_of_delegated_identity: "AnyBytes"
+
+ def __init__(
+ self,
+ *,
+ challenge_from_server: "AnyBytes",
+ size_to_acquire: "int",
+ proof_of_delegated_identity: "AnyBytes",
+ ) -> None:
+ pass
+
+ @classmethod
+ def is_type_of(cls, msg: Any) -> TypeGuard["EvoluSignRegistrationRequest"]:
+ return isinstance(msg, cls)
+
+ class EvoluRegistrationRequest(protobuf.MessageType):
+ certificate_chain: "list[AnyBytes]"
+ signature: "AnyBytes"
+
+ def __init__(
+ self,
+ *,
+ signature: "AnyBytes",
+ certificate_chain: "list[AnyBytes] | None" = None,
+ ) -> None:
+ pass
+
+ @classmethod
+ def is_type_of(cls, msg: Any) -> TypeGuard["EvoluRegistrationRequest"]:
+ return isinstance(msg, cls)
+
+ class EvoluGetDelegatedIdentityKey(protobuf.MessageType):
+ thp_credential: "AnyBytes | None"
+ host_static_public_key: "AnyBytes | None"
+
+ def __init__(
+ self,
+ *,
+ thp_credential: "AnyBytes | None" = None,
+ host_static_public_key: "AnyBytes | None" = None,
+ ) -> None:
+ pass
+
+ @classmethod
+ def is_type_of(cls, msg: Any) -> TypeGuard["EvoluGetDelegatedIdentityKey"]:
+ return isinstance(msg, cls)
+
+ class EvoluDelegatedIdentityKey(protobuf.MessageType):
+ private_key: "AnyBytes"
+
+ def __init__(
+ self,
+ *,
+ private_key: "AnyBytes",
+ ) -> None:
+ pass
+
+ @classmethod
+ def is_type_of(cls, msg: Any) -> TypeGuard["EvoluDelegatedIdentityKey"]:
+ return isinstance(msg, cls)
+
class MoneroTransactionSourceEntry(protobuf.MessageType):
outputs: "list[MoneroOutputEntry]"
real_output: "int | None"
diff --git a/core/tests/test_apps.evolu.check_evolu.py b/core/tests/test_apps.evolu.check_evolu.py
new file mode 100644
index 00000000..c324e64c
--- /dev/null
+++ b/core/tests/test_apps.evolu.check_evolu.py
@@ -0,0 +1,99 @@
+# flake8: noqa: F403,F405
+from common import * # isort:skip
+
+
+@unittest.skipUnless(utils.USE_OPTIGA, "only needed with Optiga")
+class TestCheckDelegatedIdentityKey(unittest.TestCase):
+
+ def test_sign_registration_request(self):
+ from trezorutils import delegated_identity
+ from ubinascii import unhexlify
+
+ from trezor.crypto.curve import nist256p1
+ from trezor.crypto.hashlib import sha256
+ from trezor.utils import HashWriter
+
+ from apps.common.writers import write_compact_size
+ from apps.evolu.common import check_delegated_identity_proof
+
+ # sign_registration_request
+ header = b"EvoluSignRegistrationRequest"
+ sign_request_challenge: str = "1234"
+ sign_request_size: int = 10
+ arguments = [
+ unhexlify(sign_request_challenge),
+ sign_request_size.to_bytes(4, "big"),
+ ]
+
+ h = HashWriter(sha256())
+ write_compact_size(h, len(header))
+ h.extend(header)
+
+ for arg in arguments:
+ write_compact_size(h, len(arg))
+ h.extend(arg)
+ proof = nist256p1.sign(
+ delegated_identity(),
+ h.get_digest(),
+ )
+
+ self.assertTrue(check_delegated_identity_proof(proof, header, arguments))
+
+ def test_sign_registration_request_invalid_size(self):
+ from ubinascii import unhexlify
+
+ from trezor.wire import DataError
+
+ from apps.evolu.sign_registration_request import _check_data
+
+ sign_request_challenge = unhexlify("1234")
+ sign_request_size: int = 256**4 + 5 # invalid size
+
+ with self.assertRaises(DataError):
+ _check_data(sign_request_challenge, sign_request_size)
+
+ sign_request_size: int = -1 # invalid size
+
+ with self.assertRaises(DataError):
+ _check_data(sign_request_challenge, sign_request_size)
+
+ def test_sign_registration_request_invalid_challenge(self):
+ from trezor.wire import DataError
+
+ from apps.evolu.sign_registration_request import _check_data
+
+ sign_request_challenge = b"" # invalid length
+ sign_request_size: int = 10
+
+ with self.assertRaises(DataError):
+ _check_data(sign_request_challenge, sign_request_size)
+
+ def test_get_evolu_node(self):
+ from trezorutils import delegated_identity
+
+ from trezor.crypto.curve import nist256p1
+ from trezor.crypto.hashlib import sha256
+ from trezor.utils import HashWriter
+
+ from apps.common.writers import write_compact_size
+ from apps.evolu.common import check_delegated_identity_proof
+
+ # get_node
+
+ header = b"EvoluGetNode"
+
+ h = HashWriter(sha256())
+ write_compact_size(h, len(header))
+ h.extend(header)
+
+ proof = nist256p1.sign(
+ delegated_identity(),
+ h.get_digest(),
+ )
+
+ self.assertTrue(check_delegated_identity_proof(proof, header, arguments=[]))
+ self.assertTrue(check_delegated_identity_proof(proof, header))
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/core/translations/en.json b/core/translations/en.json
index b78e0150..0963f8a7 100644
--- a/core/translations/en.json
+++ b/core/translations/en.json
@@ -1810,6 +1810,9 @@
"Delizia": "Wrong SD card.",
"Eckhart": ""
},
+ "secure_sync__delegated_identity_key_no_thp": "Allow Trezor Suite to use Suite Sync with this Trezor?",
+ "secure_sync__delegated_identity_key_thp": "Allow {0} on {1} to use Suite Sync with this Trezor?",
+ "secure_sync__header": "Suite Sync",
"send__cancel_sign": "Cancel sign",
"send__cancel_transaction": "Cancel transaction",
"send__confirm_sending": "Sending amount",
diff --git a/core/translations/order.json b/core/translations/order.json
index 726db035..5bb70186 100644
--- a/core/translations/order.json
+++ b/core/translations/order.json
@@ -1168,5 +1168,9 @@
"1166": "sn__title",
"1167": "ble__must_be_enabled",
"1168": "ripple__destination_tag_missing",
- "1169": "words__comm_trouble"
+ "1169": "words__comm_trouble",
+ "1170": "secure_sync__delegated_identity_key_no_thp",
+ "1171": "secure_sync__delegated_identity_key_thp",
+ "1172": "secure_sync__evolu_node_no_optiga",
+ "1173": "secure_sync__header"
}
diff --git a/core/translations/signatures.json b/core/translations/signatures.json
index 419f1820..091664be 100644
--- a/core/translations/signatures.json
+++ b/core/translations/signatures.json
@@ -1,8 +1,8 @@
{
"current": {
- "merkle_root": "96c528327467288b48c7355ad28626c5f070d33facf1143b08a299714bd85ae3",
- "datetime": "2025-11-03T23:52:13.709067+00:00",
- "commit": "a9d1525418508a33c35617e78a7103658a1e421e"
+ "merkle_root": "286123907f6e963dbdc20daa841609c79eed3944ea48cd80e5b64629166e7c65",
+ "datetime": "2025-11-04T12:56:15.782484+00:00",
+ "commit": "07eda3f1054f1634f8ceeff1929292ec52b40684"
},
"history": [
{
diff --git a/legacy/firmware/protob/Makefile b/legacy/firmware/protob/Makefile
index 9673c83f..0bb20148 100644
--- a/legacy/firmware/protob/Makefile
+++ b/legacy/firmware/protob/Makefile
@@ -15,7 +15,7 @@ SKIPPED_MESSAGES := Cardano DebugMonero Eos Monero Ontology Ripple SdProtect Tez
SetBrightness DebugLinkOptigaSetSecMax \
BenchmarkListNames BenchmarkRun BenchmarkNames BenchmarkResult \
NostrGetPubkey NostrPubkey NostrSignEvent NostrEventSignature \
- BleUnpair PaymentNotification EvoluGetNode EvoluNode \
+ BleUnpair PaymentNotification Evolu \
GetSerialNumber SerialNumber
ifeq ($(BITCOIN_ONLY), 1)
diff --git a/python/src/trezorlib/cli/evolu.py b/python/src/trezorlib/cli/evolu.py
index 092ca702..0a3dcedb 100644
--- a/python/src/trezorlib/cli/evolu.py
+++ b/python/src/trezorlib/cli/evolu.py
@@ -16,14 +16,14 @@
from __future__ import annotations
-import typing as t
+from typing import TYPE_CHECKING, Optional
import click
-from .. import evolu, messages
+from .. import evolu
from . import with_session
-if t.TYPE_CHECKING:
+if TYPE_CHECKING:
from ..transport.session import Session
@@ -33,15 +33,62 @@ def cli() -> None:
@cli.command()
+@click.argument("proof", type=str)
@with_session
def get_node(
- session: "Session",
-) -> dict[str, str]:
+ session: Session,
+ proof: str,
+) -> str:
"""Return the SLIP-21 node for Evolu."""
+ proof_bytes = bytes.fromhex(proof)
+ return evolu.get_node(session, proof=proof_bytes).hex()
+
+
+@cli.command()
+@click.argument("proof", type=str)
+@click.argument("challenge", type=str)
+@click.option("--size", "-s", type=int, default=1048576) # 1 MB
+@with_session
+def sign_registration_request(
+ session: Session,
+ proof: str,
+ challenge: str,
+ size: int,
+) -> dict[str, str]:
+ """Sign a registration request for this device to be registred at the Quota Manager server."""
- node: messages.EvoluNode = evolu.get_evolu_node(
- session,
+ response = evolu.sign_registration_request(
+ session=session,
+ challenge=bytes.fromhex(challenge),
+ size=size,
+ proof=bytes.fromhex(proof),
)
return {
- "data": node.data.hex(),
+ "certificates": ",".join([cert.hex() for cert in response.certificate_chain]),
+ "signature": response.signature.hex(),
}
+
+
+@click.option("--credential", "-c", type=str)
+@click.option("--pubkey", "-p", type=str)
+@cli.command()
+@with_session
+def get_delegated_identity_key(
+ session: Session,
+ credential: Optional[str] = None,
+ pubkey: Optional[str] = None,
+) -> str:
+ """
+ Request the delegated identity key of this device.
+ This key is used to prove the identity of the device at the Quota Manager server and to prove
+ to Trezor that this host has been given trust by the user to manage the Suite Sync.
+ """
+
+ thp_credential = bytes.fromhex(credential) if credential else None
+ host_static_public_key = bytes.fromhex(pubkey) if pubkey else None
+
+ return evolu.get_delegated_identity_key(
+ session=session,
+ thp_credential=thp_credential,
+ host_static_public_key=host_static_public_key,
+ ).hex()
diff --git a/python/src/trezorlib/evolu.py b/python/src/trezorlib/evolu.py
index e139c12e..77925f8d 100644
--- a/python/src/trezorlib/evolu.py
+++ b/python/src/trezorlib/evolu.py
@@ -14,8 +14,9 @@
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
+from __future__ import annotations
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Optional
from . import messages
@@ -23,8 +24,36 @@ if TYPE_CHECKING:
from .transport.session import Session
-def get_evolu_node(session: "Session") -> messages.EvoluNode:
+def get_node(session: Session, proof: bytes) -> bytes:
return session.call(
- messages.EvoluGetNode(),
+ messages.EvoluGetNode(proof_of_delegated_identity=proof),
expect=messages.EvoluNode,
+ ).data
+
+
+def sign_registration_request(
+ session: Session, challenge: bytes, size: int, proof: bytes
+) -> messages.EvoluRegistrationRequest:
+ return session.call(
+ messages.EvoluSignRegistrationRequest(
+ challenge_from_server=challenge,
+ size_to_acquire=size,
+ proof_of_delegated_identity=proof,
+ ),
+ expect=messages.EvoluRegistrationRequest,
)
+
+
+def get_delegated_identity_key(
+ session: Session,
+ thp_credential: Optional[bytes] = None,
+ host_static_public_key: Optional[bytes] = None,
+) -> bytes:
+
+ return session.call(
+ messages.EvoluGetDelegatedIdentityKey(
+ thp_credential=thp_credential,
+ host_static_public_key=host_static_public_key,
+ ),
+ expect=messages.EvoluDelegatedIdentityKey,
+ ).private_key
diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py
index e38ff863..36d157cb 100644
--- a/python/src/trezorlib/messages.py
+++ b/python/src/trezorlib/messages.py
@@ -678,6 +678,10 @@ class MessageType(IntEnum):
NostrEventSignature = 2004
EvoluGetNode = 2100
EvoluNode = 2101
+ EvoluSignRegistrationRequest = 2102
+ EvoluRegistrationRequest = 2103
+ EvoluGetDelegatedIdentityKey = 2104
+ EvoluDelegatedIdentityKey = 2105
BenchmarkListNames = 9100
BenchmarkNames = 9101
BenchmarkRun = 9102
@@ -5553,6 +5557,16 @@ class EthereumFieldType(protobuf.MessageType):
class EvoluGetNode(protobuf.MessageType):
MESSAGE_WIRE_TYPE = 2100
+ FIELDS = {
+ 1: protobuf.Field("proof_of_delegated_identity", "bytes", repeated=False, required=True),
+ }
+
+ def __init__(
+ self,
+ *,
+ proof_of_delegated_identity: "bytes",
+ ) -> None:
+ self.proof_of_delegated_identity = proof_of_delegated_identity
class EvoluNode(protobuf.MessageType):
@@ -5569,6 +5583,74 @@ class EvoluNode(protobuf.MessageType):
self.data = data
+class EvoluSignRegistrationRequest(protobuf.MessageType):
+ MESSAGE_WIRE_TYPE = 2102
+ FIELDS = {
+ 1: protobuf.Field("challenge_from_server", "bytes", repeated=False, required=True),
+ 2: protobuf.Field("size_to_acquire", "uint32", repeated=False, required=True),
+ 3: protobuf.Field("proof_of_delegated_identity", "bytes", repeated=False, required=True),
+ }
+
+ def __init__(
+ self,
+ *,
+ challenge_from_server: "bytes",
+ size_to_acquire: "int",
+ proof_of_delegated_identity: "bytes",
+ ) -> None:
+ self.challenge_from_server = challenge_from_server
+ self.size_to_acquire = size_to_acquire
+ self.proof_of_delegated_identity = proof_of_delegated_identity
+
+
+class EvoluRegistrationRequest(protobuf.MessageType):
+ MESSAGE_WIRE_TYPE = 2103
+ FIELDS = {
+ 1: protobuf.Field("certificate_chain", "bytes", repeated=True, required=False, default=None),
+ 2: protobuf.Field("signature", "bytes", repeated=False, required=True),
+ }
+
+ def __init__(
+ self,
+ *,
+ signature: "bytes",
+ certificate_chain: Optional[Sequence["bytes"]] = None,
+ ) -> None:
+ self.certificate_chain: Sequence["bytes"] = certificate_chain if certificate_chain is not None else []
+ self.signature = signature
+
+
+class EvoluGetDelegatedIdentityKey(protobuf.MessageType):
+ MESSAGE_WIRE_TYPE = 2104
+ FIELDS = {
+ 1: protobuf.Field("thp_credential", "bytes", repeated=False, required=False, default=None),
+ 2: protobuf.Field("host_static_public_key", "bytes", repeated=False, required=False, default=None),
+ }
+
+ def __init__(
+ self,
+ *,
+ thp_credential: Optional["bytes"] = None,
+ host_static_public_key: Optional["bytes"] = None,
+ ) -> None:
+ self.thp_credential = thp_credential
+ self.host_static_public_key = host_static_public_key
+
+
+class EvoluDelegatedIdentityKey(protobuf.MessageType):
+ MESSAGE_WIRE_TYPE = 2105
+ FIELDS = {
+ 1: protobuf.Field("private_key", "bytes", repeated=False, required=True),
+ }
+
+ def __init__(
+ self,
+ *,
+ private_key: "bytes",
+ ) -> None:
+ self.private_key = private_key
+
+
class MoneroTransactionSourceEntry(protobuf.MessageType):
MESSAGE_WIRE_TYPE = None
FIELDS = {
diff --git a/rust/trezor-client/src/messages/generated.rs b/rust/trezor-client/src/messages/generated.rs
index 4b696973..eb0f95b9 100644
--- a/rust/trezor-client/src/messages/generated.rs
+++ b/rust/trezor-client/src/messages/generated.rs
@@ -192,6 +192,10 @@ trezor_message_impl! {
trezor_message_impl! {
EvoluGetNode => MessageType_EvoluGetNode,
EvoluNode => MessageType_EvoluNode,
+ EvoluSignRegistrationRequest => MessageType_EvoluSignRegistrationRequest,
+ EvoluRegistrationRequest => MessageType_EvoluRegistrationRequest,
+ EvoluGetDelegatedIdentityKey => MessageType_EvoluGetDelegatedIdentityKey,
+ EvoluDelegatedIdentityKey => MessageType_EvoluDelegatedIdentityKey,
}
#[cfg(feature = "monero")]
diff --git a/rust/trezor-client/src/protos/generated/messages.rs b/rust/trezor-client/src/protos/generated/messages.rs
index 668a01c5..98bf636c 100644
--- a/rust/trezor-client/src/protos/generated/messages.rs
+++ b/rust/trezor-client/src/protos/generated/messages.rs
@@ -535,6 +535,14 @@ pub enum MessageType {
MessageType_EvoluGetNode = 2100,
// @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_EvoluNode)
MessageType_EvoluNode = 2101,
+ // @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_EvoluSignRegistrationRequest)
+ MessageType_EvoluSignRegistrationRequest = 2102,
+ // @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_EvoluRegistrationRequest)
+ MessageType_EvoluRegistrationRequest = 2103,
+ // @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_EvoluGetDelegatedIdentityKey)
+ MessageType_EvoluGetDelegatedIdentityKey = 2104,
+ // @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_EvoluDelegatedIdentityKey)
+ MessageType_EvoluDelegatedIdentityKey = 2105,
// @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_BenchmarkListNames)
MessageType_BenchmarkListNames = 9100,
// @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_BenchmarkNames)
@@ -808,6 +816,10 @@ impl ::protobuf::Enum for MessageType {
2004 => ::std::option::Option::Some(MessageType::MessageType_NostrEventSignature),
2100 => ::std::option::Option::Some(MessageType::MessageType_EvoluGetNode),
2101 => ::std::option::Option::Some(MessageType::MessageType_EvoluNode),
+ 2102 => ::std::option::Option::Some(MessageType::MessageType_EvoluSignRegistrationRequest),
+ 2103 => ::std::option::Option::Some(MessageType::MessageType_EvoluRegistrationRequest),
+ 2104 => ::std::option::Option::Some(MessageType::MessageType_EvoluGetDelegatedIdentityKey),
+ 2105 => ::std::option::Option::Some(MessageType::MessageType_EvoluDelegatedIdentityKey),
9100 => ::std::option::Option::Some(MessageType::MessageType_BenchmarkListNames),
9101 => ::std::option::Option::Some(MessageType::MessageType_BenchmarkNames),
9102 => ::std::option::Option::Some(MessageType::MessageType_BenchmarkRun),
@@ -1072,6 +1084,10 @@ impl ::protobuf::Enum for MessageType {
"MessageType_NostrEventSignature" => ::std::option::Option::Some(MessageType::MessageType_NostrEventSignature),
"MessageType_EvoluGetNode" => ::std::option::Option::Some(MessageType::MessageType_EvoluGetNode),
"MessageType_EvoluNode" => ::std::option::Option::Some(MessageType::MessageType_EvoluNode),
+ "MessageType_EvoluSignRegistrationRequest" => ::std::option::Option::Some(MessageType::MessageType_EvoluSignRegistrationRequest),
+ "MessageType_EvoluRegistrationRequest" => ::std::option::Option::Some(MessageType::MessageType_EvoluRegistrationRequest),
+ "MessageType_EvoluGetDelegatedIdentityKey" => ::std::option::Option::Some(MessageType::MessageType_EvoluGetDelegatedIdentityKey),
+ "MessageType_EvoluDelegatedIdentityKey" => ::std::option::Option::Some(MessageType::MessageType_EvoluDelegatedIdentityKey),
"MessageType_BenchmarkListNames" => ::std::option::Option::Some(MessageType::MessageType_BenchmarkListNames),
"MessageType_BenchmarkNames" => ::std::option::Option::Some(MessageType::MessageType_BenchmarkNames),
"MessageType_BenchmarkRun" => ::std::option::Option::Some(MessageType::MessageType_BenchmarkRun),
@@ -1335,6 +1351,10 @@ impl ::protobuf::Enum for MessageType {
MessageType::MessageType_NostrEventSignature,
MessageType::MessageType_EvoluGetNode,
MessageType::MessageType_EvoluNode,
+ MessageType::MessageType_EvoluSignRegistrationRequest,
+ MessageType::MessageType_EvoluRegistrationRequest,
+ MessageType::MessageType_EvoluGetDelegatedIdentityKey,
+ MessageType::MessageType_EvoluDelegatedIdentityKey,
MessageType::MessageType_BenchmarkListNames,
MessageType::MessageType_BenchmarkNames,
MessageType::MessageType_BenchmarkRun,
@@ -1604,10 +1624,14 @@ impl ::protobuf::EnumFull for MessageType {
MessageType::MessageType_NostrEventSignature => 251,
MessageType::MessageType_EvoluGetNode => 252,
MessageType::MessageType_EvoluNode => 253,
- MessageType::MessageType_BenchmarkListNames => 254,
- MessageType::MessageType_BenchmarkNames => 255,
- MessageType::MessageType_BenchmarkRun => 256,
- MessageType::MessageType_BenchmarkResult => 257,
+ MessageType::MessageType_EvoluSignRegistrationRequest => 254,
+ MessageType::MessageType_EvoluRegistrationRequest => 255,
+ MessageType::MessageType_EvoluGetDelegatedIdentityKey => 256,
+ MessageType::MessageType_EvoluDelegatedIdentityKey => 257,
+ MessageType::MessageType_BenchmarkListNames => 258,
+ MessageType::MessageType_BenchmarkNames => 259,
+ MessageType::MessageType_BenchmarkRun => 260,
+ MessageType::MessageType_BenchmarkResult => 261,
};
Self::enum_descriptor().value_by_index(index)
}
@@ -1626,7 +1650,7 @@ impl MessageType {
}
static file_descriptor_proto_data: &'static [u8] = b"\
- \n\x0emessages.proto\x12\x12hw.trezor.messages\x1a\roptions.proto*\xffY\
+ \n\x0emessages.proto\x12\x12hw.trezor.messages\x1a\roptions.proto*\xdc[\
\n\x0bMessageType\x12(\n\x16MessageType_Initialize\x10\0\x1a\x0c\x80\xa6\
\x1d\x01\xb0\xb5\x18\x01\x90\xb5\x18\x01\x12\x1e\n\x10MessageType_Ping\
\x10\x01\x1a\x08\x80\xa6\x1d\x01\x90\xb5\x18\x01\x12%\n\x13MessageType_S\
@@ -1917,16 +1941,22 @@ static file_descriptor_proto_data: &'static [u8] = b"\
eType_NostrEventSignature\x10\xd4\x0f\x1a\x04\x98\xb5\x18\x01\x12'\n\x18\
MessageType_EvoluGetNode\x10\xb4\x10\x1a\x08\x80\xa6\x1d\x01\x90\xb5\x18\
\x01\x12$\n\x15MessageType_EvoluNode\x10\xb5\x10\x1a\x08\x80\xa6\x1d\x01\
- \x98\xb5\x18\x01\x12)\n\x1eMessageType_BenchmarkListNames\x10\x8cG\x1a\
- \x04\x80\xa6\x1d\x01\x12%\n\x1aMessageType_BenchmarkNames\x10\x8dG\x1a\
- \x04\x80\xa6\x1d\x01\x12#\n\x18MessageType_BenchmarkRun\x10\x8eG\x1a\x04\
- \x80\xa6\x1d\x01\x12&\n\x1bMessageType_BenchmarkResult\x10\x8fG\x1a\x04\
- \x80\xa6\x1d\x01\x1a\x08\xc8\xf3\x18\x01\xd0\xf3\x18\x01\"\x04\x08Z\x10\
- \\\"\x04\x08G\x10J\"\x04\x08r\x10z\"\x05\x08{\x10\x95\x01\"\x06\x08\xdb\
- \x01\x10\xdb\x01\"\x06\x08\xe0\x01\x10\xe0\x01\"\x06\x08\xac\x02\x10\xb0\
- \x02\"\x06\x08\xb5\x02\x10\xb8\x02\"\x06\x08\xbc\x05\x10\xc5\x05\"\x06\
- \x08\xe9\x07\x10\xf7\x07\"\x06\x08\xfa\x07\x10\xcb\x08B8\n#com.satoshila\
- bs.trezor.lib.protobufB\rTrezorMessage\x80\xa6\x1d\x01\
+ \x98\xb5\x18\x01\x127\n(MessageType_EvoluSignRegistrationRequest\x10\xb6\
+ \x10\x1a\x08\x80\xa6\x1d\x01\x90\xb5\x18\x01\x123\n$MessageType_EvoluReg\
+ istrationRequest\x10\xb7\x10\x1a\x08\x80\xa6\x1d\x01\x98\xb5\x18\x01\x12\
+ 7\n(MessageType_EvoluGetDelegatedIdentityKey\x10\xb8\x10\x1a\x08\x80\xa6\
+ \x1d\x01\x90\xb5\x18\x01\x124\n%MessageType_EvoluDelegatedIdentityKey\
+ \x10\xb9\x10\x1a\x08\x80\xa6\x1d\x01\x98\xb5\x18\x01\x12)\n\x1eMessageTy\
+ pe_BenchmarkListNames\x10\x8cG\x1a\x04\x80\xa6\x1d\x01\x12%\n\x1aMessage\
+ Type_BenchmarkNames\x10\x8dG\x1a\x04\x80\xa6\x1d\x01\x12#\n\x18MessageTy\
+ pe_BenchmarkRun\x10\x8eG\x1a\x04\x80\xa6\x1d\x01\x12&\n\x1bMessageType_B\
+ enchmarkResult\x10\x8fG\x1a\x04\x80\xa6\x1d\x01\x1a\x08\xc8\xf3\x18\x01\
+ \xd0\xf3\x18\x01\"\x04\x08Z\x10\\\"\x04\x08G\x10J\"\x04\x08r\x10z\"\x05\
+ \x08{\x10\x95\x01\"\x06\x08\xdb\x01\x10\xdb\x01\"\x06\x08\xe0\x01\x10\
+ \xe0\x01\"\x06\x08\xac\x02\x10\xb0\x02\"\x06\x08\xb5\x02\x10\xb8\x02\"\
+ \x06\x08\xbc\x05\x10\xc5\x05\"\x06\x08\xe9\x07\x10\xf7\x07\"\x06\x08\xfa\
+ \x07\x10\xcb\x08B8\n#com.satoshilabs.trezor.lib.protobufB\rTrezorMessage\
+ \x80\xa6\x1d\x01\
";
/// `FileDescriptorProto` object which was a source for this generated file
diff --git a/rust/trezor-client/src/protos/generated/messages_evolu.rs b/rust/trezor-client/src/protos/generated/messages_evolu.rs
index e67faab3..73991578 100644
--- a/rust/trezor-client/src/protos/generated/messages_evolu.rs
+++ b/rust/trezor-client/src/protos/generated/messages_evolu.rs
@@ -27,6 +27,9 @@ const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_3_7_2;
// @@protoc_insertion_point(message:hw.trezor.messages.evolu.EvoluGetNode)
#[derive(PartialEq,Clone,Default,Debug)]
pub struct EvoluGetNode {
+ // message fields
+ // @@protoc_insertion_point(field:hw.trezor.messages.evolu.EvoluGetNode.proof_of_delegated_identity)
+ pub proof_of_delegated_identity: ::std::option::Option<::std::vec::Vec<u8>>,
// special fields
// @@protoc_insertion_point(special_field:hw.trezor.messages.evolu.EvoluGetNode.special_fields)
pub special_fields: ::protobuf::SpecialFields,
@@ -43,9 +46,50 @@ impl EvoluGetNode {
::std::default::Default::default()
}
+ // required bytes proof_of_delegated_identity = 1;
+
+ pub fn proof_of_delegated_identity(&self) -> &[u8] {
+ match self.proof_of_delegated_identity.as_ref() {
+ Some(v) => v,
+ None => &[],
+ }
+ }
+
+ pub fn clear_proof_of_delegated_identity(&mut self) {
+ self.proof_of_delegated_identity = ::std::option::Option::None;
+ }
+
+ pub fn has_proof_of_delegated_identity(&self) -> bool {
+ self.proof_of_delegated_identity.is_some()
+ }
+
+ // Param is passed by value, moved
+ pub fn set_proof_of_delegated_identity(&mut self, v: ::std::vec::Vec<u8>) {
+ self.proof_of_delegated_identity = ::std::option::Option::Some(v);
+ }
+
+ // Mutable pointer to the field.
+ // If field is not initialized, it is initialized with default value first.
+ pub fn mut_proof_of_delegated_identity(&mut self) -> &mut ::std::vec::Vec<u8> {
+ if self.proof_of_delegated_identity.is_none() {
+ self.proof_of_delegated_identity = ::std::option::Option::Some(::std::vec::Vec::new());
+ }
+ self.proof_of_delegated_identity.as_mut().unwrap()
+ }
+
+ // Take field
+ pub fn take_proof_of_delegated_identity(&mut self) -> ::std::vec::Vec<u8> {
+ self.proof_of_delegated_identity.take().unwrap_or_else(|| ::std::vec::Vec::new())
+ }
+
fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData {
- let mut fields = ::std::vec::Vec::with_capacity(0);
+ let mut fields = ::std::vec::Vec::with_capacity(1);
let mut oneofs = ::std::vec::Vec::with_capacity(0);
+ fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>(
+ "proof_of_delegated_identity",
+ |m: &EvoluGetNode| { &m.proof_of_delegated_identity },
+ |m: &mut EvoluGetNode| { &mut m.proof_of_delegated_identity },
+ ));
::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<EvoluGetNode>(
"EvoluGetNode",
fields,
@@ -58,12 +102,18 @@ impl ::protobuf::Message for EvoluGetNode {
const NAME: &'static str = "EvoluGetNode";
fn is_initialized(&self) -> bool {
+ if self.proof_of_delegated_identity.is_none() {
+ return false;
+ }
true
}
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> {
while let Some(tag) = is.read_raw_tag_or_eof()? {
match tag {
+ 10 => {
+ self.proof_of_delegated_identity = ::std::option::Option::Some(is.read_bytes()?);
+ },
tag => {
::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;
},
@@ -76,12 +126,18 @@ impl ::protobuf::Message for EvoluGetNode {
#[allow(unused_variables)]
fn compute_size(&self) -> u64 {
let mut my_size = 0;
+ if let Some(v) = self.proof_of_delegated_identity.as_ref() {
+ my_size += ::protobuf::rt::bytes_size(1, &v);
+ }
my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields());
self.special_fields.cached_size().set(my_size as u32);
my_size
}
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> {
+ if let Some(v) = self.proof_of_delegated_identity.as_ref() {
+ os.write_bytes(1, v)?;
+ }
os.write_unknown_fields(self.special_fields.unknown_fields())?;
::std::result::Result::Ok(())
}
@@ -99,11 +155,13 @@ impl ::protobuf::Message for EvoluGetNode {
}
fn clear(&mut self) {
+ self.proof_of_delegated_identity = ::std::option::Option::None;
self.special_fields.clear();
}
fn default_instance() -> &'static EvoluGetNode {
static instance: EvoluGetNode = EvoluGetNode {
+ proof_of_delegated_identity: ::std::option::Option::None,
special_fields: ::protobuf::SpecialFields::new(),
};
&instance
@@ -288,11 +346,832 @@ impl ::protobuf::reflect::ProtobufValue for EvoluNode {
type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage<Self>;
}
+// @@protoc_insertion_point(message:hw.trezor.messages.evolu.EvoluSignRegistrationRequest)
+#[derive(PartialEq,Clone,Default,Debug)]
+pub struct EvoluSignRegistrationRequest {
+ // message fields
+ // @@protoc_insertion_point(field:hw.trezor.messages.evolu.EvoluSignRegistrationRequest.challenge_from_server)
+ pub challenge_from_server: ::std::option::Option<::std::vec::Vec<u8>>,
+ // @@protoc_insertion_point(field:hw.trezor.messages.evolu.EvoluSignRegistrationRequest.size_to_acquire)
+ pub size_to_acquire: ::std::option::Option<u32>,
+ // @@protoc_insertion_point(field:hw.trezor.messages.evolu.EvoluSignRegistrationRequest.proof_of_delegated_identity)
+ pub proof_of_delegated_identity: ::std::option::Option<::std::vec::Vec<u8>>,
+ // special fields
+ // @@protoc_insertion_point(special_field:hw.trezor.messages.evolu.EvoluSignRegistrationRequest.special_fields)
+ pub special_fields: ::protobuf::SpecialFields,
+}
+
+impl<'a> ::std::default::Default for &'a EvoluSignRegistrationRequest {
+ fn default() -> &'a EvoluSignRegistrationRequest {
+ <EvoluSignRegistrationRequest as ::protobuf::Message>::default_instance()
+ }
+}
+
+impl EvoluSignRegistrationRequest {
+ pub fn new() -> EvoluSignRegistrationRequest {
+ ::std::default::Default::default()
+ }
+
+ // required bytes challenge_from_server = 1;
+
+ pub fn challenge_from_server(&self) -> &[u8] {
+ match self.challenge_from_server.as_ref() {
+ Some(v) => v,
+ None => &[],
+ }
+ }
+
+ pub fn clear_challenge_from_server(&mut self) {
+ self.challenge_from_server = ::std::option::Option::None;
+ }
+
+ pub fn has_challenge_from_server(&self) -> bool {
+ self.challenge_from_server.is_some()
+ }
+
+ // Param is passed by value, moved
+ pub fn set_challenge_from_server(&mut self, v: ::std::vec::Vec<u8>) {
+ self.challenge_from_server = ::std::option::Option::Some(v);
+ }
+
+ // Mutable pointer to the field.
+ // If field is not initialized, it is initialized with default value first.
+ pub fn mut_challenge_from_server(&mut self) -> &mut ::std::vec::Vec<u8> {
+ if self.challenge_from_server.is_none() {
+ self.challenge_from_server = ::std::option::Option::Some(::std::vec::Vec::new());
+ }
+ self.challenge_from_server.as_mut().unwrap()
+ }
+
+ // Take field
+ pub fn take_challenge_from_server(&mut self) -> ::std::vec::Vec<u8> {
+ self.challenge_from_server.take().unwrap_or_else(|| ::std::vec::Vec::new())
+ }
+
+ // required uint32 size_to_acquire = 2;
+
+ pub fn size_to_acquire(&self) -> u32 {
+ self.size_to_acquire.unwrap_or(0)
+ }
+
+ pub fn clear_size_to_acquire(&mut self) {
+ self.size_to_acquire = ::std::option::Option::None;
+ }
+
+ pub fn has_size_to_acquire(&self) -> bool {
+ self.size_to_acquire.is_some()
+ }
+
+ // Param is passed by value, moved
+ pub fn set_size_to_acquire(&mut self, v: u32) {
+ self.size_to_acquire = ::std::option::Option::Some(v);
+ }
+
+ // required bytes proof_of_delegated_identity = 3;
+
+ pub fn proof_of_delegated_identity(&self) -> &[u8] {
+ match self.proof_of_delegated_identity.as_ref() {
+ Some(v) => v,
+ None => &[],
+ }
+ }
+
+ pub fn clear_proof_of_delegated_identity(&mut self) {
+ self.proof_of_delegated_identity = ::std::option::Option::None;
+ }
+
+ pub fn has_proof_of_delegated_identity(&self) -> bool {
+ self.proof_of_delegated_identity.is_some()
+ }
+
+ // Param is passed by value, moved
+ pub fn set_proof_of_delegated_identity(&mut self, v: ::std::vec::Vec<u8>) {
+ self.proof_of_delegated_identity = ::std::option::Option::Some(v);
+ }
+
+ // Mutable pointer to the field.
+ // If field is not initialized, it is initialized with default value first.
+ pub fn mut_proof_of_delegated_identity(&mut self) -> &mut ::std::vec::Vec<u8> {
+ if self.proof_of_delegated_identity.is_none() {
+ self.proof_of_delegated_identity = ::std::option::Option::Some(::std::vec::Vec::new());
+ }
+ self.proof_of_delegated_identity.as_mut().unwrap()
+ }
+
+ // Take field
+ pub fn take_proof_of_delegated_identity(&mut self) -> ::std::vec::Vec<u8> {
+ self.proof_of_delegated_identity.take().unwrap_or_else(|| ::std::vec::Vec::new())
+ }
+
+ fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData {
+ let mut fields = ::std::vec::Vec::with_capacity(3);
+ let mut oneofs = ::std::vec::Vec::with_capacity(0);
+ fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>(
+ "challenge_from_server",
+ |m: &EvoluSignRegistrationRequest| { &m.challenge_from_server },
+ |m: &mut EvoluSignRegistrationRequest| { &mut m.challenge_from_server },
+ ));
+ fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>(
+ "size_to_acquire",
+ |m: &EvoluSignRegistrationRequest| { &m.size_to_acquire },
+ |m: &mut EvoluSignRegistrationRequest| { &mut m.size_to_acquire },
+ ));
+ fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>(
+ "proof_of_delegated_identity",
+ |m: &EvoluSignRegistrationRequest| { &m.proof_of_delegated_identity },
+ |m: &mut EvoluSignRegistrationRequest| { &mut m.proof_of_delegated_identity },
+ ));
+ ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<EvoluSignRegistrationRequest>(
+ "EvoluSignRegistrationRequest",
+ fields,
+ oneofs,
+ )
+ }
+}
+
+impl ::protobuf::Message for EvoluSignRegistrationRequest {
+ const NAME: &'static str = "EvoluSignRegistrationRequest";
+
+ fn is_initialized(&self) -> bool {
+ if self.challenge_from_server.is_none() {
+ return false;
+ }
+ if self.size_to_acquire.is_none() {
+ return false;
+ }
+ if self.proof_of_delegated_identity.is_none() {
+ return false;
+ }
+ true
+ }
+
+ fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> {
+ while let Some(tag) = is.read_raw_tag_or_eof()? {
+ match tag {
+ 10 => {
+ self.challenge_from_server = ::std::option::Option::Some(is.read_bytes()?);
+ },
+ 16 => {
+ self.size_to_acquire = ::std::option::Option::Some(is.read_uint32()?);
+ },
+ 26 => {
+ self.proof_of_delegated_identity = ::std::option::Option::Some(is.read_bytes()?);
+ },
+ tag => {
+ ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;
+ },
+ };
+ }
+ ::std::result::Result::Ok(())
+ }
+
+ // Compute sizes of nested messages
+ #[allow(unused_variables)]
+ fn compute_size(&self) -> u64 {
+ let mut my_size = 0;
+ if let Some(v) = self.challenge_from_server.as_ref() {
+ my_size += ::protobuf::rt::bytes_size(1, &v);
+ }
+ if let Some(v) = self.size_to_acquire {
+ my_size += ::protobuf::rt::uint32_size(2, v);
+ }
+ if let Some(v) = self.proof_of_delegated_identity.as_ref() {
+ my_size += ::protobuf::rt::bytes_size(3, &v);
+ }
+ my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields());
+ self.special_fields.cached_size().set(my_size as u32);
+ my_size
+ }
+
+ fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> {
+ if let Some(v) = self.challenge_from_server.as_ref() {
+ os.write_bytes(1, v)?;
+ }
+ if let Some(v) = self.size_to_acquire {
+ os.write_uint32(2, v)?;
+ }
+ if let Some(v) = self.proof_of_delegated_identity.as_ref() {
+ os.write_bytes(3, v)?;
+ }
+ os.write_unknown_fields(self.special_fields.unknown_fields())?;
+ ::std::result::Result::Ok(())
+ }
+
+ fn special_fields(&self) -> &::protobuf::SpecialFields {
+ &self.special_fields
+ }
+
+ fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields {
+ &mut self.special_fields
+ }
+
+ fn new() -> EvoluSignRegistrationRequest {
+ EvoluSignRegistrationRequest::new()
+ }
+
+ fn clear(&mut self) {
+ self.challenge_from_server = ::std::option::Option::None;
+ self.size_to_acquire = ::std::option::Option::None;
+ self.proof_of_delegated_identity = ::std::option::Option::None;
+ self.special_fields.clear();
+ }
+
+ fn default_instance() -> &'static EvoluSignRegistrationRequest {
+ static instance: EvoluSignRegistrationRequest = EvoluSignRegistrationRequest {
+ challenge_from_server: ::std::option::Option::None,
+ size_to_acquire: ::std::option::Option::None,
+ proof_of_delegated_identity: ::std::option::Option::None,
+ special_fields: ::protobuf::SpecialFields::new(),
+ };
+ &instance
+ }
+}
+
+impl ::protobuf::MessageFull for EvoluSignRegistrationRequest {
+ fn descriptor() -> ::protobuf::reflect::MessageDescriptor {
+ static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new();
+ descriptor.get(|| file_descriptor().message_by_package_relative_name("EvoluSignRegistrationRequest").unwrap()).clone()
+ }
+}
+
+impl ::std::fmt::Display for EvoluSignRegistrationRequest {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+ ::protobuf::text_format::fmt(self, f)
+ }
+}
+
+impl ::protobuf::reflect::ProtobufValue for EvoluSignRegistrationRequest {
+ type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage<Self>;
+}
+
+// @@protoc_insertion_point(message:hw.trezor.messages.evolu.EvoluRegistrationRequest)
+#[derive(PartialEq,Clone,Default,Debug)]
+pub struct EvoluRegistrationRequest {
+ // message fields
+ // @@protoc_insertion_point(field:hw.trezor.messages.evolu.EvoluRegistrationRequest.certificate_chain)
+ pub certificate_chain: ::std::vec::Vec<::std::vec::Vec<u8>>,
+ // @@protoc_insertion_point(field:hw.trezor.messages.evolu.EvoluRegistrationRequest.signature)
+ pub signature: ::std::option::Option<::std::vec::Vec<u8>>,
+ // special fields
+ // @@protoc_insertion_point(special_field:hw.trezor.messages.evolu.EvoluRegistrationRequest.special_fields)
+ pub special_fields: ::protobuf::SpecialFields,
+}
+
+impl<'a> ::std::default::Default for &'a EvoluRegistrationRequest {
+ fn default() -> &'a EvoluRegistrationRequest {
+ <EvoluRegistrationRequest as ::protobuf::Message>::default_instance()
+ }
+}
+
+impl EvoluRegistrationRequest {
+ pub fn new() -> EvoluRegistrationRequest {
+ ::std::default::Default::default()
+ }
+
+ // required bytes signature = 2;
+
+ pub fn signature(&self) -> &[u8] {
+ match self.signature.as_ref() {
+ Some(v) => v,
+ None => &[],
+ }
+ }
+
+ pub fn clear_signature(&mut self) {
+ self.signature = ::std::option::Option::None;
+ }
+
+ pub fn has_signature(&self) -> bool {
+ self.signature.is_some()
+ }
+
+ // Param is passed by value, moved
+ pub fn set_signature(&mut self, v: ::std::vec::Vec<u8>) {
+ self.signature = ::std::option::Option::Some(v);
+ }
+
+ // Mutable pointer to the field.
+ // If field is not initialized, it is initialized with default value first.
+ pub fn mut_signature(&mut self) -> &mut ::std::vec::Vec<u8> {
+ if self.signature.is_none() {
+ self.signature = ::std::option::Option::Some(::std::vec::Vec::new());
+ }
+ self.signature.as_mut().unwrap()
+ }
+
+ // Take field
+ pub fn take_signature(&mut self) -> ::std::vec::Vec<u8> {
+ self.signature.take().unwrap_or_else(|| ::std::vec::Vec::new())
+ }
+
+ fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData {
+ let mut fields = ::std::vec::Vec::with_capacity(2);
+ let mut oneofs = ::std::vec::Vec::with_capacity(0);
+ fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>(
+ "certificate_chain",
+ |m: &EvoluRegistrationRequest| { &m.certificate_chain },
+ |m: &mut EvoluRegistrationRequest| { &mut m.certificate_chain },
+ ));
+ fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>(
+ "signature",
+ |m: &EvoluRegistrationRequest| { &m.signature },
+ |m: &mut EvoluRegistrationRequest| { &mut m.signature },
+ ));
+ ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<EvoluRegistrationRequest>(
+ "EvoluRegistrationRequest",
+ fields,
+ oneofs,
+ )
+ }
+}
+
+impl ::protobuf::Message for EvoluRegistrationRequest {
+ const NAME: &'static str = "EvoluRegistrationRequest";
+
+ fn is_initialized(&self) -> bool {
+ if self.signature.is_none() {
+ return false;
+ }
+ true
+ }
+
+ fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> {
+ while let Some(tag) = is.read_raw_tag_or_eof()? {
+ match tag {
+ 10 => {
+ self.certificate_chain.push(is.read_bytes()?);
+ },
+ 18 => {
+ self.signature = ::std::option::Option::Some(is.read_bytes()?);
+ },
+ tag => {
+ ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;
+ },
+ };
+ }
+ ::std::result::Result::Ok(())
+ }
+
+ // Compute sizes of nested messages
+ #[allow(unused_variables)]
+ fn compute_size(&self) -> u64 {
+ let mut my_size = 0;
+ for value in &self.certificate_chain {
+ my_size += ::protobuf::rt::bytes_size(1, &value);
+ };
+ if let Some(v) = self.signature.as_ref() {
+ my_size += ::protobuf::rt::bytes_size(2, &v);
+ }
+ my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields());
+ self.special_fields.cached_size().set(my_size as u32);
+ my_size
+ }
+
+ fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> {
+ for v in &self.certificate_chain {
+ os.write_bytes(1, &v)?;
+ };
+ if let Some(v) = self.signature.as_ref() {
+ os.write_bytes(2, v)?;
+ }
+ os.write_unknown_fields(self.special_fields.unknown_fields())?;
+ ::std::result::Result::Ok(())
+ }
+
+ fn special_fields(&self) -> &::protobuf::SpecialFields {
+ &self.special_fields
+ }
+
+ fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields {
+ &mut self.special_fields
+ }
+
+ fn new() -> EvoluRegistrationRequest {
+ EvoluRegistrationRequest::new()
+ }
+
+ fn clear(&mut self) {
+ self.certificate_chain.clear();
+ self.signature = ::std::option::Option::None;
+ self.special_fields.clear();
+ }
+
+ fn default_instance() -> &'static EvoluRegistrationRequest {
+ static instance: EvoluRegistrationRequest = EvoluRegistrationRequest {
+ certificate_chain: ::std::vec::Vec::new(),
+ signature: ::std::option::Option::None,
+ special_fields: ::protobuf::SpecialFields::new(),
+ };
+ &instance
+ }
+}
+
+impl ::protobuf::MessageFull for EvoluRegistrationRequest {
+ fn descriptor() -> ::protobuf::reflect::MessageDescriptor {
+ static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new();
+ descriptor.get(|| file_descriptor().message_by_package_relative_name("EvoluRegistrationRequest").unwrap()).clone()
+ }
+}
+
+impl ::std::fmt::Display for EvoluRegistrationRequest {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+ ::protobuf::text_format::fmt(self, f)
+ }
+}
+
+impl ::protobuf::reflect::ProtobufValue for EvoluRegistrationRequest {
+ type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage<Self>;
+}
+
+// @@protoc_insertion_point(message:hw.trezor.messages.evolu.EvoluGetDelegatedIdentityKey)
+#[derive(PartialEq,Clone,Default,Debug)]
+pub struct EvoluGetDelegatedIdentityKey {
+ // message fields
+ // @@protoc_insertion_point(field:hw.trezor.messages.evolu.EvoluGetDelegatedIdentityKey.thp_credential)
+ pub thp_credential: ::std::option::Option<::std::vec::Vec<u8>>,
+ // @@protoc_insertion_point(field:hw.trezor.messages.evolu.EvoluGetDelegatedIdentityKey.host_static_public_key)
+ pub host_static_public_key: ::std::option::Option<::std::vec::Vec<u8>>,
+ // special fields
+ // @@protoc_insertion_point(special_field:hw.trezor.messages.evolu.EvoluGetDelegatedIdentityKey.special_fields)
+ pub special_fields: ::protobuf::SpecialFields,
+}
+
+impl<'a> ::std::default::Default for &'a EvoluGetDelegatedIdentityKey {
+ fn default() -> &'a EvoluGetDelegatedIdentityKey {
+ <EvoluGetDelegatedIdentityKey as ::protobuf::Message>::default_instance()
+ }
+}
+
+impl EvoluGetDelegatedIdentityKey {
+ pub fn new() -> EvoluGetDelegatedIdentityKey {
+ ::std::default::Default::default()
+ }
+
+ // optional bytes thp_credential = 1;
+
+ pub fn thp_credential(&self) -> &[u8] {
+ match self.thp_credential.as_ref() {
+ Some(v) => v,
+ None => &[],
+ }
+ }
+
+ pub fn clear_thp_credential(&mut self) {
+ self.thp_credential = ::std::option::Option::None;
+ }
+
+ pub fn has_thp_credential(&self) -> bool {
+ self.thp_credential.is_some()
+ }
+
+ // Param is passed by value, moved
+ pub fn set_thp_credential(&mut self, v: ::std::vec::Vec<u8>) {
+ self.thp_credential = ::std::option::Option::Some(v);
+ }
+
+ // Mutable pointer to the field.
+ // If field is not initialized, it is initialized with default value first.
+ pub fn mut_thp_credential(&mut self) -> &mut ::std::vec::Vec<u8> {
+ if self.thp_credential.is_none() {
+ self.thp_credential = ::std::option::Option::Some(::std::vec::Vec::new());
+ }
+ self.thp_credential.as_mut().unwrap()
+ }
+
+ // Take field
+ pub fn take_thp_credential(&mut self) -> ::std::vec::Vec<u8> {
+ self.thp_credential.take().unwrap_or_else(|| ::std::vec::Vec::new())
+ }
+
+ // optional bytes host_static_public_key = 2;
+
+ pub fn host_static_public_key(&self) -> &[u8] {
+ match self.host_static_public_key.as_ref() {
+ Some(v) => v,
+ None => &[],
+ }
+ }
+
+ pub fn clear_host_static_public_key(&mut self) {
+ self.host_static_public_key = ::std::option::Option::None;
+ }
+
+ pub fn has_host_static_public_key(&self) -> bool {
+ self.host_static_public_key.is_some()
+ }
+
+ // Param is passed by value, moved
+ pub fn set_host_static_public_key(&mut self, v: ::std::vec::Vec<u8>) {
+ self.host_static_public_key = ::std::option::Option::Some(v);
+ }
+
+ // Mutable pointer to the field.
+ // If field is not initialized, it is initialized with default value first.
+ pub fn mut_host_static_public_key(&mut self) -> &mut ::std::vec::Vec<u8> {
+ if self.host_static_public_key.is_none() {
+ self.host_static_public_key = ::std::option::Option::Some(::std::vec::Vec::new());
+ }
+ self.host_static_public_key.as_mut().unwrap()
+ }
+
+ // Take field
+ pub fn take_host_static_public_key(&mut self) -> ::std::vec::Vec<u8> {
+ self.host_static_public_key.take().unwrap_or_else(|| ::std::vec::Vec::new())
+ }
+
+ fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData {
+ let mut fields = ::std::vec::Vec::with_capacity(2);
+ let mut oneofs = ::std::vec::Vec::with_capacity(0);
+ fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>(
+ "thp_credential",
+ |m: &EvoluGetDelegatedIdentityKey| { &m.thp_credential },
+ |m: &mut EvoluGetDelegatedIdentityKey| { &mut m.thp_credential },
+ ));
+ fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>(
+ "host_static_public_key",
+ |m: &EvoluGetDelegatedIdentityKey| { &m.host_static_public_key },
+ |m: &mut EvoluGetDelegatedIdentityKey| { &mut m.host_static_public_key },
+ ));
+ ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<EvoluGetDelegatedIdentityKey>(
+ "EvoluGetDelegatedIdentityKey",
+ fields,
+ oneofs,
+ )
+ }
+}
+
+impl ::protobuf::Message for EvoluGetDelegatedIdentityKey {
+ const NAME: &'static str = "EvoluGetDelegatedIdentityKey";
+
+ fn is_initialized(&self) -> bool {
+ true
+ }
+
+ fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> {
+ while let Some(tag) = is.read_raw_tag_or_eof()? {
+ match tag {
+ 10 => {
+ self.thp_credential = ::std::option::Option::Some(is.read_bytes()?);
+ },
+ 18 => {
+ self.host_static_public_key = ::std::option::Option::Some(is.read_bytes()?);
+ },
+ tag => {
+ ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;
+ },
+ };
+ }
+ ::std::result::Result::Ok(())
+ }
+
+ // Compute sizes of nested messages
+ #[allow(unused_variables)]
+ fn compute_size(&self) -> u64 {
+ let mut my_size = 0;
+ if let Some(v) = self.thp_credential.as_ref() {
+ my_size += ::protobuf::rt::bytes_size(1, &v);
+ }
+ if let Some(v) = self.host_static_public_key.as_ref() {
+ my_size += ::protobuf::rt::bytes_size(2, &v);
+ }
+ my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields());
+ self.special_fields.cached_size().set(my_size as u32);
+ my_size
+ }
+
+ fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> {
+ if let Some(v) = self.thp_credential.as_ref() {
+ os.write_bytes(1, v)?;
+ }
+ if let Some(v) = self.host_static_public_key.as_ref() {
+ os.write_bytes(2, v)?;
+ }
+ os.write_unknown_fields(self.special_fields.unknown_fields())?;
+ ::std::result::Result::Ok(())
+ }
+
+ fn special_fields(&self) -> &::protobuf::SpecialFields {
+ &self.special_fields
+ }
+
+ fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields {
+ &mut self.special_fields
+ }
+
+ fn new() -> EvoluGetDelegatedIdentityKey {
+ EvoluGetDelegatedIdentityKey::new()
+ }
+
+ fn clear(&mut self) {
+ self.thp_credential = ::std::option::Option::None;
+ self.host_static_public_key = ::std::option::Option::None;
+ self.special_fields.clear();
+ }
+
+ fn default_instance() -> &'static EvoluGetDelegatedIdentityKey {
+ static instance: EvoluGetDelegatedIdentityKey = EvoluGetDelegatedIdentityKey {
+ thp_credential: ::std::option::Option::None,
+ host_static_public_key: ::std::option::Option::None,
+ special_fields: ::protobuf::SpecialFields::new(),
+ };
+ &instance
+ }
+}
+
+impl ::protobuf::MessageFull for EvoluGetDelegatedIdentityKey {
+ fn descriptor() -> ::protobuf::reflect::MessageDescriptor {
+ static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new();
+ descriptor.get(|| file_descriptor().message_by_package_relative_name("EvoluGetDelegatedIdentityKey").unwrap()).clone()
+ }
+}
+
+impl ::std::fmt::Display for EvoluGetDelegatedIdentityKey {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+ ::protobuf::text_format::fmt(self, f)
+ }
+}
+
+impl ::protobuf::reflect::ProtobufValue for EvoluGetDelegatedIdentityKey {
+ type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage<Self>;
+}
+
+// @@protoc_insertion_point(message:hw.trezor.messages.evolu.EvoluDelegatedIdentityKey)
+#[derive(PartialEq,Clone,Default,Debug)]
+pub struct EvoluDelegatedIdentityKey {
+ // message fields
+ // @@protoc_insertion_point(field:hw.trezor.messages.evolu.EvoluDelegatedIdentityKey.private_key)
+ pub private_key: ::std::option::Option<::std::vec::Vec<u8>>,
+ // special fields
+ // @@protoc_insertion_point(special_field:hw.trezor.messages.evolu.EvoluDelegatedIdentityKey.special_fields)
+ pub special_fields: ::protobuf::SpecialFields,
+}
+
+impl<'a> ::std::default::Default for &'a EvoluDelegatedIdentityKey {
+ fn default() -> &'a EvoluDelegatedIdentityKey {
+ <EvoluDelegatedIdentityKey as ::protobuf::Message>::default_instance()
+ }
+}
+
+impl EvoluDelegatedIdentityKey {
+ pub fn new() -> EvoluDelegatedIdentityKey {
+ ::std::default::Default::default()
+ }
+
+ // required bytes private_key = 1;
+
+ pub fn private_key(&self) -> &[u8] {
+ match self.private_key.as_ref() {
+ Some(v) => v,
+ None => &[],
+ }
+ }
+
+ pub fn clear_private_key(&mut self) {
+ self.private_key = ::std::option::Option::None;
+ }
+
+ pub fn has_private_key(&self) -> bool {
+ self.private_key.is_some()
+ }
+
+ // Param is passed by value, moved
+ pub fn set_private_key(&mut self, v: ::std::vec::Vec<u8>) {
+ self.private_key = ::std::option::Option::Some(v);
+ }
+
+ // Mutable pointer to the field.
+ // If field is not initialized, it is initialized with default value first.
+ pub fn mut_private_key(&mut self) -> &mut ::std::vec::Vec<u8> {
+ if self.private_key.is_none() {
+ self.private_key = ::std::option::Option::Some(::std::vec::Vec::new());
+ }
+ self.private_key.as_mut().unwrap()
+ }
+
+ // Take field
+ pub fn take_private_key(&mut self) -> ::std::vec::Vec<u8> {
+ self.private_key.take().unwrap_or_else(|| ::std::vec::Vec::new())
+ }
+
+ fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData {
+ let mut fields = ::std::vec::Vec::with_capacity(1);
+ let mut oneofs = ::std::vec::Vec::with_capacity(0);
+ fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>(
+ "private_key",
+ |m: &EvoluDelegatedIdentityKey| { &m.private_key },
+ |m: &mut EvoluDelegatedIdentityKey| { &mut m.private_key },
+ ));
+ ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<EvoluDelegatedIdentityKey>(
+ "EvoluDelegatedIdentityKey",
+ fields,
+ oneofs,
+ )
+ }
+}
+
+impl ::protobuf::Message for EvoluDelegatedIdentityKey {
+ const NAME: &'static str = "EvoluDelegatedIdentityKey";
+
+ fn is_initialized(&self) -> bool {
+ if self.private_key.is_none() {
+ return false;
+ }
+ true
+ }
+
+ fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> {
+ while let Some(tag) = is.read_raw_tag_or_eof()? {
+ match tag {
+ 10 => {
+ self.private_key = ::std::option::Option::Some(is.read_bytes()?);
+ },
+ tag => {
+ ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;
+ },
+ };
+ }
+ ::std::result::Result::Ok(())
+ }
+
+ // Compute sizes of nested messages
+ #[allow(unused_variables)]
+ fn compute_size(&self) -> u64 {
+ let mut my_size = 0;
+ if let Some(v) = self.private_key.as_ref() {
+ my_size += ::protobuf::rt::bytes_size(1, &v);
+ }
+ my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields());
+ self.special_fields.cached_size().set(my_size as u32);
+ my_size
+ }
+
+ fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> {
+ if let Some(v) = self.private_key.as_ref() {
+ os.write_bytes(1, v)?;
+ }
+ os.write_unknown_fields(self.special_fields.unknown_fields())?;
+ ::std::result::Result::Ok(())
+ }
+
+ fn special_fields(&self) -> &::protobuf::SpecialFields {
+ &self.special_fields
+ }
+
+ fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields {
+ &mut self.special_fields
+ }
+
+ fn new() -> EvoluDelegatedIdentityKey {
+ EvoluDelegatedIdentityKey::new()
+ }
+
+ fn clear(&mut self) {
+ self.private_key = ::std::option::Option::None;
+ self.special_fields.clear();
+ }
+
+ fn default_instance() -> &'static EvoluDelegatedIdentityKey {
+ static instance: EvoluDelegatedIdentityKey = EvoluDelegatedIdentityKey {
+ private_key: ::std::option::Option::None,
+ special_fields: ::protobuf::SpecialFields::new(),
+ };
+ &instance
+ }
+}
+
+impl ::protobuf::MessageFull for EvoluDelegatedIdentityKey {
+ fn descriptor() -> ::protobuf::reflect::MessageDescriptor {
+ static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new();
+ descriptor.get(|| file_descriptor().message_by_package_relative_name("EvoluDelegatedIdentityKey").unwrap()).clone()
+ }
+}
+
+impl ::std::fmt::Display for EvoluDelegatedIdentityKey {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+ ::protobuf::text_format::fmt(self, f)
+ }
+}
+
+impl ::protobuf::reflect::ProtobufValue for EvoluDelegatedIdentityKey {
+ type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage<Self>;
+}
+
static file_descriptor_proto_data: &'static [u8] = b"\
\n\x14messages-evolu.proto\x12\x18hw.trezor.messages.evolu\x1a\roptions.\
- proto\"\x0e\n\x0cEvoluGetNode\"\x1f\n\tEvoluNode\x12\x12\n\x04data\x18\
- \x01\x20\x02(\x0cR\x04dataB=\n#com.satoshilabs.trezor.lib.protobufB\x12T\
- rezorMessageEvolu\x80\xa6\x1d\x01\
+ proto\"M\n\x0cEvoluGetNode\x12=\n\x1bproof_of_delegated_identity\x18\x01\
+ \x20\x02(\x0cR\x18proofOfDelegatedIdentity\"\x1f\n\tEvoluNode\x12\x12\n\
+ \x04data\x18\x01\x20\x02(\x0cR\x04data\"\xb9\x01\n\x1cEvoluSignRegistrat\
+ ionRequest\x122\n\x15challenge_from_server\x18\x01\x20\x02(\x0cR\x13chal\
+ lengeFromServer\x12&\n\x0fsize_to_acquire\x18\x02\x20\x02(\rR\rsizeToAcq\
+ uire\x12=\n\x1bproof_of_delegated_identity\x18\x03\x20\x02(\x0cR\x18proo\
+ fOfDelegatedIdentity\"e\n\x18EvoluRegistrationRequest\x12+\n\x11certific\
+ ate_chain\x18\x01\x20\x03(\x0cR\x10certificateChain\x12\x1c\n\tsignature\
+ \x18\x02\x20\x02(\x0cR\tsignature\"z\n\x1cEvoluGetDelegatedIdentityKey\
+ \x12%\n\x0ethp_credential\x18\x01\x20\x01(\x0cR\rthpCredential\x123\n\
+ \x16host_static_public_key\x18\x02\x20\x01(\x0cR\x13hostStaticPublicKey\
+ \"<\n\x19EvoluDelegatedIdentityKey\x12\x1f\n\x0bprivate_key\x18\x01\x20\
+ \x02(\x0cR\nprivateKeyB=\n#com.satoshilabs.trezor.lib.protobufB\x12Trezo\
+ rMessageEvolu\x80\xa6\x1d\x01\
";
/// `FileDescriptorProto` object which was a source for this generated file
@@ -311,9 +1190,13 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor {
let generated_file_descriptor = generated_file_descriptor_lazy.get(|| {
let mut deps = ::std::vec::Vec::with_capacity(1);
deps.push(super::options::file_descriptor().clone());
- let mut messages = ::std::vec::Vec::with_capacity(2);
+ let mut messages = ::std::vec::Vec::with_capacity(6);
messages.push(EvoluGetNode::generated_message_descriptor_data());
messages.push(EvoluNode::generated_message_descriptor_data());
+ messages.push(EvoluSignRegistrationRequest::generated_message_descriptor_data());
+ messages.push(EvoluRegistrationRequest::generated_message_descriptor_data());
+ messages.push(EvoluGetDelegatedIdentityKey::generated_message_descriptor_data());
+ messages.push(EvoluDelegatedIdentityKey::generated_message_descriptor_data());
let mut enums = ::std::vec::Vec::with_capacity(0);
::protobuf::reflect::GeneratedFileDescriptor::new_generated(
file_descriptor_proto(),
diff --git a/tests/device_tests/evolu/__init__.py b/tests/device_tests/evolu/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/device_tests/evolu/test_get_delegated_identity_key.py b/tests/device_tests/evolu/test_get_delegated_identity_key.py
new file mode 100644
index 00000000..930a36a4
--- /dev/null
+++ b/tests/device_tests/evolu/test_get_delegated_identity_key.py
@@ -0,0 +1,22 @@
+import pytest
+
+from trezorlib import evolu
+from trezorlib.debuglink import SessionDebugWrapper as Session
+
+pytestmark = [pytest.mark.models("core"), pytest.mark.protocol("protocol_v1")]
+
+
+def test_evolu_get_delegated_identity_is_constant(session: Session):
+ private_key = evolu.get_delegated_identity_key(session)
+ assert len(private_key) == 32
+
+ private_key_2 = evolu.get_delegated_identity_key(session)
+ assert private_key_2 == private_key
+
+
+def test_evolu_get_delegated_identity_test_vector(session: Session):
+ # on emulator, the master key is all zeroes. So the delegated identity key is constant.
+ private_key = evolu.get_delegated_identity_key(session)
+ assert private_key == bytes.fromhex(
+ "10e39ed3a40dd63a47a14608d4bccd4501170cf9f2188223208084d39c37b369"
+ )
diff --git a/tests/device_tests/evolu/test_get_delegated_identity_key_thp.py b/tests/device_tests/evolu/test_get_delegated_identity_key_thp.py
new file mode 100644
index 00000000..8e49fada
--- /dev/null
+++ b/tests/device_tests/evolu/test_get_delegated_identity_key_thp.py
@@ -0,0 +1,100 @@
+import os
+
+import pytest
+
+from trezorlib.debuglink import SessionDebugWrapper as Session
+from trezorlib.debuglink import TrezorClientDebugLink as Client
+from trezorlib.messages import (
+ EvoluDelegatedIdentityKey,
+ EvoluGetDelegatedIdentityKey,
+ ThpCredentialResponse,
+)
+from trezorlib.transport.thp import curve25519
+
+pytestmark = [pytest.mark.protocol("protocol_v2"), pytest.mark.models("core")]
+
+TEST_host_static_private_key = curve25519.get_private_key(os.urandom(32))
+TEST_host_static_public_key = curve25519.get_public_key(TEST_host_static_private_key)
+
+
+class ThpPairingResult:
+ def __init__(self, session, credential):
+ self.session: Session = session
+ self.credential: ThpCredentialResponse = credential
+
+
+def pair_and_get_credential(client: Client) -> ThpPairingResult:
+ from trezorlib.messages import (
+ ThpCredentialRequest,
+ ThpCredentialResponse,
+ ThpEndRequest,
+ ThpEndResponse,
+ )
+
+ from ..thp.connect import prepare_protocol_for_pairing
+ from ..thp.test_pairing import nfc_pairing
+
+ protocol = prepare_protocol_for_pairing(client)
+ nfc_pairing(client, protocol)
+ protocol._send_message(
+ ThpCredentialRequest(
+ host_static_public_key=TEST_host_static_public_key,
+ autoconnect=False,
+ )
+ )
+ credential_response = protocol._read_message(ThpCredentialResponse)
+
+ protocol._send_message(ThpEndRequest())
+ protocol._read_message(ThpEndResponse)
+ protocol._is_paired = True
+
+ client.protocol = protocol
+ session = client.get_session()
+ return ThpPairingResult(session, credential_response)
+
+
+def test_evolu_get_delegated_identity_is_constant(client: Client):
+ pairing_data = pair_and_get_credential(client)
+ credential_data = pairing_data.credential
+ session = pairing_data.session
+
+ response = session.call(
+ EvoluGetDelegatedIdentityKey(
+ thp_credential=credential_data.credential,
+ host_static_public_key=TEST_host_static_public_key,
+ ),
+ expect=EvoluDelegatedIdentityKey,
+ )
+
+ private_key = response.private_key
+ assert len(private_key) == 32
+
+ response_2 = session.call(
+ EvoluGetDelegatedIdentityKey(
+ thp_credential=credential_data.credential,
+ host_static_public_key=TEST_host_static_public_key,
+ ),
+ expect=EvoluDelegatedIdentityKey,
+ )
+ assert response_2.private_key == private_key
+
+
+def test_evolu_get_delegated_identity_test_vector(client: Client):
+ # on emulator, the master key is all zeroes. So the delegated identity key is constant.
+
+ pairing_data = pair_and_get_credential(client)
+ credential_data = pairing_data.credential
+ session = pairing_data.session
+
+ response = session.call(
+ EvoluGetDelegatedIdentityKey(
+ thp_credential=credential_data.credential,
+ host_static_public_key=TEST_host_static_public_key,
+ ),
+ expect=EvoluDelegatedIdentityKey,
+ )
+
+ private_key = response.private_key
+ assert private_key == bytes.fromhex(
+ "10e39ed3a40dd63a47a14608d4bccd4501170cf9f2188223208084d39c37b369"
+ )
diff --git a/tests/device_tests/evolu/test_get_node.py b/tests/device_tests/evolu/test_get_node.py
new file mode 100644
index 00000000..fec1f950
--- /dev/null
+++ b/tests/device_tests/evolu/test_get_node.py
@@ -0,0 +1,47 @@
+import pytest
+
+from trezorlib import evolu
+from trezorlib.debuglink import SessionDebugWrapper as Session
+from trezorlib.exceptions import TrezorFailure
+
+pytestmark = pytest.mark.models("core")
+
+
+def test_evolu_get_node(session: Session):
+ proof = bytes.fromhex(
+ "1fb521e8a4e4580377d530a9d6eb0a394ec8340fa42094d9f2e822bb944ce6a2074b81241b3b65dfa15d66e052f2504aba3ad1644844d695b181b3cdc9666cb66b"
+ )
+ node = evolu.get_node(session, proof=proof)
+
+ check_value = bytes.fromhex(
+ "a81aaf51997b6ddfa33d11c038d6aba5f711754a2c823823ff8b777825cdbb32b0e71c301fa381c75081bd3bcc134b63306aa6fc9a9f52d835ad4df8cd507be6"
+ )
+ assert node == check_value
+
+
+def test_evolu_get_node_invalid_proof(session: Session):
+ proof = bytes.fromhex(
+ "1f354fbb47b4679c1cb0c2c6b96a27f9a147c61ec5ef6f6c42491c839f4b7a95792d099be0f138274e5ef7896058b4de4f383f497792bb157b925e2644a79a0000" # altered last 2 bytes
+ )
+
+ with pytest.raises(
+ TrezorFailure,
+ match="Invalid proof",
+ ):
+ evolu.get_node(session, proof=proof)
+
+
+def test_evolu_get_node_no_proof(session: Session):
+ with pytest.raises(
+ TrezorFailure,
+ match="Invalid proof",
+ ):
+ evolu.get_node(session, proof=b"")
+
+
+def test_evolu_get_node_none_proof(session: Session):
+ with pytest.raises(
+ TrezorFailure,
+ match="DataError: Failed to decode message: Missing required field. proof_of_delegated_identity",
+ ):
+ evolu.get_node(session, proof=None) # type: ignore
diff --git a/tests/device_tests/evolu/test_sign_registration.py b/tests/device_tests/evolu/test_sign_registration.py
new file mode 100644
index 00000000..4c5d52b1
--- /dev/null
+++ b/tests/device_tests/evolu/test_sign_registration.py
@@ -0,0 +1,168 @@
+import pytest
+
+from trezorlib import evolu
+from trezorlib.debuglink import SessionDebugWrapper as Session
+from trezorlib.exceptions import TrezorFailure
+
+pytestmark = pytest.mark.models("core")
+
+
+@pytest.mark.models("t2t1")
+def test_evolu_sign_request_t2t1(session: Session):
+ challenge = "1234"
+ size = 10
+ proposed_value = bytes.fromhex(
+ "1b161be2bfc622b4ffd9943138ab5931e77b4c6835e29b1ac25221c74492495a912c00f488fd5f95b43085f721f36574813785c011c60cf81877ccd057df6bed0c"
+ )
+
+ with pytest.raises(
+ TrezorFailure,
+ match="Optiga is not available",
+ ):
+ evolu.sign_registration_request(
+ session,
+ challenge=bytes.fromhex(challenge),
+ size=size,
+ proof=proposed_value,
+ )
+
+
+@pytest.mark.models("safe")
+def test_evolu_sign_request(session: Session):
+ challenge = "1234"
+ size = 10
+ proposed_value = bytes.fromhex(
+ "1fb4ca7b8d956cc50ac652e383691af8e59b200adedde3a898b86795fd94d49241559a1699de1110617a91c44c70c4b9509fdb36f5057a52c0ef28fce7afa10734"
+ )
+ response = evolu.sign_registration_request(
+ session,
+ challenge=bytes.fromhex(challenge),
+ size=size,
+ proof=proposed_value,
+ )
+
+ check_signature = bytes.fromhex(
+ "30440220148c0a0026828532e5a2e7ce5cf2dcd2491e7eea5f5c6eafd49779d1502c5ba102204b1ca171045969e38ac815de09462d6c5b496d04851266fe71abcf55b9aee672"
+ )
+
+ assert response.signature == check_signature
+
+
+@pytest.mark.models("safe")
+def test_evolu_sign_request_invalid_proof(session: Session):
+ challenge = "1234"
+ size = 10
+ proposed_value = bytes.fromhex(
+ "20dc125b51c2f596df4a9ae9ef816353dcdbf068b91ac687962742b8bd434276f60258c337e0d03211e599701a87cae8d8ac3258ce01bd484921743c2a5e990000" # altered last 2 bytes
+ )
+
+ with pytest.raises(
+ TrezorFailure,
+ match="Invalid proof",
+ ):
+ evolu.sign_registration_request(
+ session,
+ challenge=bytes.fromhex(challenge),
+ size=size,
+ proof=proposed_value,
+ )
+
+
+@pytest.mark.models("safe")
+def test_evolu_sign_request_challenge_too_long(session: Session):
+ challenge = "01" * 300 # 300 bytes, max is 255
+ size = 10
+ proposed_value = bytes.fromhex(
+ "1fd0b4cd0a04806eaa74ae59cc2f5a740680fc784b877deff6ffa6b9eda7d5a7d4207958c48e679b18c64d0e7fcd0e5be25eb27bcf186fbf9531eb20bce7234a23"
+ )
+
+ with pytest.raises(
+ TrezorFailure,
+ match="Invalid challenge length",
+ ):
+ evolu.sign_registration_request(
+ session,
+ challenge=bytes.fromhex(challenge),
+ size=size,
+ proof=proposed_value,
+ )
+
+
+@pytest.mark.models("safe")
+def test_evolu_sign_request_challenge_too_short(session: Session):
+ challenge = "" # 0 bytes, min is 1
+ size = 10
+ proposed_value = bytes.fromhex(
+ "1fa386d20efb38dbb3f7ae0509651fa36c8128324ef89fa1cfd104e10dced08c594f0e8f0a525a839b4fbfaa92b8c2b51163cef593f5c14fc9f1c8c48d1192270d"
+ )
+
+ with pytest.raises(
+ TrezorFailure,
+ match="Invalid challenge length",
+ ):
+ evolu.sign_registration_request(
+ session,
+ challenge=bytes.fromhex(challenge),
+ size=size,
+ proof=proposed_value,
+ )
+
+
+@pytest.mark.models("safe")
+def test_evolu_sign_request_size_too_small(session: Session):
+ challenge = "1234"
+ size = -10
+ proposed_value = bytes.fromhex(
+ "1fa386d20efb38dbb3f7ae0509651fa36c8128324ef89fa1cfd104e10dced08c594f0e8f0a525a839b4fbfaa92b8c2b51163cef593f5c14fc9f1c8c48d1192270d"
+ )
+
+ with pytest.raises(
+ ValueError,
+ match=f"Value {size} in field size_to_acquire does not fit into uint32",
+ ):
+ evolu.sign_registration_request(
+ session,
+ challenge=bytes.fromhex(challenge),
+ size=size,
+ proof=proposed_value,
+ )
+
+
+@pytest.mark.models("safe")
+def test_evolu_sign_request_size_too_large(session: Session):
+ challenge = "1234"
+ size = 0xFFFFFFFF + 1
+ proposed_value = bytes.fromhex(
+ "1fa386d20efb38dbb3f7ae0509651fa36c8128324ef89fa1cfd104e10dced08c594f0e8f0a525a839b4fbfaa92b8c2b51163cef593f5c14fc9f1c8c48d1192270d"
+ )
+
+ with pytest.raises(
+ ValueError,
+ match=f"Value {size} in field size_to_acquire does not fit into uint32",
+ ):
+ evolu.sign_registration_request(
+ session,
+ challenge=bytes.fromhex(challenge),
+ size=size,
+ proof=proposed_value,
+ )
+
+
+@pytest.mark.models("safe")
+def test_evolu_sign_request_data_higher_bound(session: Session):
+ challenge = "12" * 255
+ size = 0xFFFFFFFF
+ proposed_value = bytes.fromhex(
+ "1f1971f6ce302562e737520c0de2338cdaaac4e676fa02ff857b3b6081ebde794545f25905128ae9c9e7861e2358fe2e94821dd9e902564ec11478e5c6b60527c8"
+ )
+
+ response = evolu.sign_registration_request(
+ session,
+ challenge=bytes.fromhex(challenge),
+ size=size,
+ proof=proposed_value,
+ )
+ check_signature = bytes.fromhex(
+ "304402202fedb9dee42c4cb19c27daab8c5f8cbfb74047fa65a5521e1f410a14cb0ab41502202d76b31fe1c97e4577191825bc39e0b01a3bafcba3615130175cbe11d5714832"
+ )
+ assert response.signature == check_signature
diff --git a/tests/device_tests/misc/test_msg_getevolunode.py b/tests/device_tests/misc/test_msg_getevolunode.py
deleted file mode 100644
index e2dc6fba..00000000
--- a/tests/device_tests/misc/test_msg_getevolunode.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# This file is part of the Trezor project.
-#
-# Copyright (C) 2012-2025 SatoshiLabs and contributors
-#
-# This library is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License version 3
-# as published by the Free Software Foundation.
-#
-# This library 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 Lesser General Public License for more details.
-#
-# You should have received a copy of the License along with this library.
-# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
-
-import pytest
-
-from trezorlib import evolu
-from trezorlib.debuglink import SessionDebugWrapper as Session
-
-pytestmark = [pytest.mark.altcoin, pytest.mark.models("core")]
-
-EXPECTED_SLIP21_NODE_DATA = "a81aaf51997b6ddfa33d11c038d6aba5f711754a2c823823ff8b777825cdbb32b0e71c301fa381c75081bd3bcc134b63306aa6fc9a9f52d835ad4df8cd507be6"
-
-
-def test_get_evolu_node(session: Session):
- """Test Evolu key derivation against known test vectors."""
- res = evolu.get_evolu_node(session)
-
- assert res.data.hex() == EXPECTED_SLIP21_NODE_DATA
diff --git a/tests/device_tests/thp/test_pairing.py b/tests/device_tests/thp/test_pairing.py
index d83e4648..f2cb6be6 100644
--- a/tests/device_tests/thp/test_pairing.py
+++ b/tests/device_tests/thp/test_pairing.py
@@ -241,14 +241,14 @@ def test_pairing_cancel_2(client: Client) -> None:
def test_pairing_nfc(client: Client) -> None:
protocol = prepare_protocol_for_pairing(client)
- _nfc_pairing(client, protocol)
+ nfc_pairing(client, protocol)
protocol._send_message(ThpEndRequest())
protocol._read_message(ThpEndResponse)
protocol._is_paired = True
-def _nfc_pairing(client: Client, protocol: ProtocolV2Channel) -> None:
+def nfc_pairing(client: Client, protocol: ProtocolV2Channel) -> None:
handle_pairing_request(client, protocol, "TestTrezor NfcPairing")
@@ -291,7 +291,7 @@ def _nfc_pairing(client: Client, protocol: ProtocolV2Channel) -> None:
def test_connection_confirmation_cancel(client: Client) -> None:
protocol = prepare_protocol_for_pairing(client)
- _nfc_pairing(client, protocol)
+ nfc_pairing(client, protocol)
# Request credential with confirmation after pairing
randomness_static = os.urandom(32)
@@ -335,7 +335,7 @@ def test_connection_confirmation_cancel(client: Client) -> None:
def test_autoconnect_credential_request_cancel(client: Client) -> None:
protocol = prepare_protocol_for_pairing(client)
- _nfc_pairing(client, protocol)
+ nfc_pairing(client, protocol)
# Request credential with confirmation after pairing
randomness_static = os.urandom(32)
@@ -376,7 +376,7 @@ def test_autoconnect_credential_request_cancel(client: Client) -> None:
def test_credential_phase(client: Client) -> None:
protocol = prepare_protocol_for_pairing(client)
- _nfc_pairing(client, protocol)
+ nfc_pairing(client, protocol)
# Request credential with confirmation after pairing
randomness_static = os.urandom(32)
@@ -474,7 +474,7 @@ def test_credential_phase(client: Client) -> None:
def test_credential_request_in_encrypted_transport_phase(client: Client) -> None:
randomness_static = os.urandom(32)
protocol = prepare_protocol_for_pairing(client, randomness_static)
- _nfc_pairing(client, protocol)
+ nfc_pairing(client, protocol)
# Request credential with confirmation after pairing
host_static_private_key = curve25519.get_private_key(randomness_static)
Why this scored 45/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.